14

I recently came across an issue where I was able to change the IEnumerable object that I was iterating over in a foreach loop. It's my understanding that in C#, you aren't supposed to be able to edit the list you're iterating over, but after some frustration, I found that this is exactly what was happening. I basically looped through a LINQ query and used the object IDs to make changes in the database on those objects and those changes affected the values in the .Where() statement.

Does anybody have an explanation for this? It seems like the LINQ query re-runs every time it's iterated over

NOTE: The fix for this is adding .ToList() after the .Where(), but my question is why this issue is happening at all i.e. if it's a bug or something I'm unaware of

using System;
using System.Linq;

namespace MyTest {
    class Program {
        static void Main () {
            var aArray = new string[] {
                "a", "a", "a", "a"
            };
            var i = 3;
            var linqObj = aArray.Where(x => x == "a");
            foreach (var item in linqObj ) {
                aArray[i] = "b";
                i--;
            }
            foreach (var arrItem in aArray) {
                Console.WriteLine(arrItem); //Why does this only print out 2 a's and 2 b's, rather than 4 b's?
            }
            Console.ReadKey();
        }
    }
}

This code is just a reproducible mockup, but I'd expect it to loop through 4 times and change all of the strings in aArray into b's. However, it only loops through twice and turns the last two strings in aArray into b's

EDIT: After some feedback and to be more concise, my main question here is this: "Why am I able to change what I'm looping over as I'm looping over it". Looks like the overwhelming answer is that LINQ does deferred execution, so it's re-evaluating as I'm looping through the LINQ IEnumerable.

EDIT 2: Actually looking through, it seems that everyone is concerned with the .Count() function, thinking that is what the issue here is. However, you can comment out that line and I still have the issue of the LINQ object changing. I updated the code to reflect the main issue

Joosh1337
  • 153
  • 9
  • 1
    `linqLIST` is a deferred executed filtering of the `aArray` array. Contrary to the name, it is *not* a list, so if you've read that changing a list while iterating over it is not allowed, will throw an exception, this does not necessarily apply here. Since the array has no way of informing the lazily evaluated enumerator about changes inside, the enumerator will not stop working, instead, as it gets to each item it will filter it depending on the predicate. If the elements change underneath it, this will impact filtering items not yet filtered. – Lasse V. Karlsen Apr 30 '19 at 14:52
  • Since it is deferred, `.Count()` will in fact force an evaluation, *every time*, which is why it keeps changing. – Lasse V. Karlsen Apr 30 '19 at 14:52
  • Now, the answers already provided here are correct, but you have asked like 3-4 different, but related, questions here so to provide a full, covering, answer is actually too broad. – Lasse V. Karlsen Apr 30 '19 at 14:53
  • 2
    Your questions are, if I got them right: 1) Why am I allowed to change a "list" while I'm enumerating over it? 2) Why does `.Count()` in this case keeps changing? 3) Does a LINQ query re-run every time you iterate over it? (short answer: yes, which probably should answer all the other questions combined) 4) How does foreach really work? – Lasse V. Karlsen Apr 30 '19 at 14:54
  • 2
    look this playground if you want to understand [sharpLab](https://sharplab.io/#v2:CYLg1APgAgDABFAjAbgLAChYMQOgDICWAdgI5qYDM2AbAgEzYDscA3hnBwlUrVACxwAsgENiACgCUrdp1kA3YQCc4wgIKLFwgJ5wAvHCIBTAO7YYAbQC609LLv24AImGOANE5fvnbj45kPOAF9yAPklOAI9OAoQ0I4FZQAbYhI8AEkAZQAVKLUNbRwAdQALQ0VDMQAPPQA+OGrdfW8JWLiAMwB7cuEAY2K4MQSIgBdDAFsIojhk0nTsqTZbOLskAE4xR2SAZ2G4PuEiAHNDRzgwaZS5rJwAYQ6AVyJhyRa4AHo3kp0CLbhh0rgWwIAC9DHAOm0/sUfhcdntigdDsRDgB+DgZYoPRLAFRwQikOAke5lHQdIiJHQAIzBhgUiXuwlGOLJPTBjKhYJ2Sl2ENxiQ6HQADij/MsOHlNFpzARrE1KY5WmKIgBaZWKgKBUUBTrdPoDIZKRRpUYTYgqdSShZa5ZrMSG43jFrW+yapZxUWuwJAA===) – valerian Havaux Apr 30 '19 at 14:54
  • Does Linq re-evaluate at each iteration of the foreach loop? (Ignoring the Count()) – Brosto Apr 30 '19 at 15:18
  • I never said the `Count` would be any issue, it’s the linq expression the gets executed every time it is called. Cast it to a `List` – Raul Apr 30 '19 at 15:35
  • @LasseVågsætherKarlsen Sorry about that, I added an edit to make the question more concise. In the end, Deferred Execution answered any question I had, I just wasn't aware that it executed each time in a foreach loop, that seems like it'd be unwanted behavior per the nature of foreach – Joosh1337 Apr 30 '19 at 15:42
  • @RaulSebastian Just wanted to make sure it was clear that the issue happened with or without `.Count()`, no worries! – Joosh1337 Apr 30 '19 at 15:43
  • 2
    You should not change questions origin after people gave answer. – ilkerkaran Apr 30 '19 at 15:47
  • @ilkerkaran Sorry, I just tried to make it a bit more concise per some confusion in the comments/answers. I feel like the question still matches the title/origin – Joosh1337 Apr 30 '19 at 16:13

5 Answers5

22

Why am I able to edit a LINQ list while iterating over it?

All of the answers that say that this is because of deferred "lazy" execution are wrong, in the sense that they do not adequately address the question that was asked: "Why am I able to edit a list while iterating over it?" Deferred execution explains why running the query twice gives different results, but does not address why the operation described in the question is possible.

The problem is actually that the original poster has a false belief:

I recently came across an issue where I was able to change the IEnumerable object that I was iterating over in a foreach loop. It's my understanding that in C#, you aren't supposed to be able to edit the list you're iterating over

Your understanding is wrong, and that's where the confusion comes from. The rule in C# is not "it is impossible to edit an enumerable from within an enumeration". The rule is you are not supposed to edit an enumerable from within an enumeration, and if you choose to do so, arbitrarily bad things can happen.

Basically what you're doing is running a stop sign and then asking "Running a stop sign is illegal, so why did the police not prevent me from running the stop sign?" The police are not required to prevent you from doing an illegal act; you are responsible for not making the attempt in the first place, and if you choose to do so, you take the chance of getting a ticket, or causing a traffic accident, or any other bad consequence of your poor choice. Usually the consequences of running a stop sign are no consequences at all, but that does not mean that it's a good idea.

Editing an enumerable while you're enumerating it is a bad practice, but the runtime is not required to be a traffic cop and prevent you from doing so. Nor is it required to flag the operation as illegal with an exception. It may do so, and sometimes it does do so, but there is not a requirement that it does so consistently.

You've found a case where the runtime does not detect the problem and does not throw an exception, but you do get a result that you find unexpected. That's fine. You broke the rules, and this time it just happens that the consequence of breaking the rules was an unexpected outcome. The runtime is not required to make the consequence of breaking the rules into an exception.

If you tried to do the same thing where, say, you called Add on a List<T> while enumerating the list, you'd get an exception because someone wrote code in List<T> that detects that situation.

No one wrote that code for "linq over an array", and so, no exception. The authors of LINQ were not required to write that code; you were required to not write the code you wrote! You chose to write a bad program that violates the rules, and the runtime is not required to catch you every time you write a bad program.

It seems like the LINQ query re-runs every time it's iterated over

That is correct. A query is a question about a data structure. If you change that data structure, the answer to the question can change. Enumerating the query answers the question.

However, that is an entirely different issue than the one in the title of your question. You really have two questions here:

  • Why can I edit an enumerable while I am enumerating it?

You can do this bad practice because nothing stops you from writing a bad program except your good sense; write better programs that do not do this!

  • Does a query re-execute from scratch every time I enumerate it?

Yes; a query is a question, not an answer. An enumeration of the query is an answer, and the answer can change over time.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • This answer may not answer what OP seeking, but it explains the fundamental of iteration and basicly ays; "Dont mess with enumerations in iterations" +1 for that – ilkerkaran Apr 30 '19 at 15:53
  • I believe you're splitting hairs here. The OP said, even in your quoted text, "you are not supposed to edit the list you're iterating over". Your entire post seems to be re-enforcing why it's a bad idea to do this in an overly aggressive way. – Brosto Apr 30 '19 at 15:57
  • 12
    @Brosto: I suggest that you spend your time responding to errors of *fact* rather than complaining about *tone*. It is unfortunate that clearly stating facts and clearly stating when other answers are wrong is perceived as "aggression", rather than as well-intended correction. I don't want people to have wrong beliefs about C#; correcting their wrong beliefs is a kindness. – Eric Lippert Apr 30 '19 at 15:59
  • 7
    @Brosto: I am not "splitting hairs"; I am disabusing the original poster of their incorrect belief. It is *wrong* to believe that "you're not supposed to be able to edit the list you're iterating over". The correct statement is "you're not supposed to edit the list you're iterating over", and those are *very different statements*. One implies a *requirement* of the *runtime*, and the other implies a requirement on the *program author*. Getting that distinction right is of crucial importance when you're trying to write a correct program. – Eric Lippert Apr 30 '19 at 16:04
  • I wasn't intentionally changing the objects in the list, as I stated in the question, I know that's bad practice/not allowed. I was instead grabbing IDs from the object and changing them in the database, which had unforeseen circumstances (at least to me). The issue here is that I wasn't aware that LINQ evaluated each iteration of the foreach loop, which is something others managed to answer concisely without an aggressive rant – Joosh1337 Apr 30 '19 at 16:11
  • 11
    @Joosh1337: Your code changes objects in an array as you're iterating over the array, not a database, and asks -- twice -- why am I able to edit a list while editing it? If that's not the question you wanted an answer to, that's not the question you should have asked! There is no "rant" intended here; your question implied that you have a number of false beliefs. I want you to be a successful C# programmer, and successful C# programmers do not have false beliefs about the language! – Eric Lippert Apr 30 '19 at 16:14
  • 4
    "All of the answers that say that this is because of deferred "lazy" execution are wrong." How so? The fact that `Where` evaluates each item immediately when requested is precisely the mechanism that *allows* the changes to be observed while iterating, which is the question asked. The question author doesn't think it's impossible because there's some general rule that all iterators must throw when their underlying data source changes, they are expecting to see the values before the change because they think that LINQ methods return the results of the queries, not a query. – Servy Apr 30 '19 at 16:15
  • 3
    Additionally there is no "rule" that `IEnumerator` objects never be allowed to observe changes over any underlying data sources while they are iterating them. That's a *convention* that's *often* a good idea, but there are situations where it's useful to do so anyway. Certain types of changes make it impossible, or very difficult, for open iterators to sensibly continue. Other types of changes don't negatively impact open iterators. Doing this, when done improperly, can be confusing or create invalid results. But none of this has anything to do with the question of why it's possible. – Servy Apr 30 '19 at 16:16
  • 7
    @Servy: They are wrong not in the sense that they are incorrect about the existence of deferred execution, but rather in the implication that deferred execution explains the confusion of the original poster; I consider an answer which contains true statements but *misleadingly implies that it addresses the root of the question* to be "wrong answers". Now, you might argue that tonally it would be better to say "irrelevant" or "unsound reasoning", or some word other than "wrong", but again, that's responding to tone. – Eric Lippert Apr 30 '19 at 16:24
  • 6
    @Servy: Your second comment is a good summary of exactly what I am attempting to communicate in this answer; thanks for that summary. **Violation of conventions can lead to unexpected results but does not determine the possibility of writing a convention-violating program**. – Eric Lippert Apr 30 '19 at 16:25
  • 6
    Now, as for tone, I encourage you all to read the answer again. Every sentence is a simple, straightforward declarative sentence that states facts. If you read simple, straightforward declarative sentences which state facts as "aggressive" and "ranting" then I encourage you to first, read more charitably, and second, ask yourself why you have an emotional reaction to statements of fact. – Eric Lippert Apr 30 '19 at 16:32
  • @EricLippert The question demonstrates confusion as to why iterating over a collection would return the current values of the collection rather than the values at the time the query was generated. It doesn't demonstrate confusion as to why iterating over a collection that changed wouldn't throw an exception of some kind due to the collection changing during iteration. Thus your answer *does not answer the question* of why and how this is even possible, instead saying that the toy example that's solely there to demonstrate a point isn't "good practice", which is just irrelevant to the question. – Servy Apr 30 '19 at 17:23
  • 4
    @Servy: I'm answering the question that was asked, which is "Why am I able to edit a LINQ list while iterating over it?" and I'm addressing a misunderstanding directly stated by the original poster: "it's my understanding that in C#, you aren't supposed to be able to edit the list you're iterating over". I don't see why this is controversial; we should answer the questions that were asked, and we should disabuse people of their incorrect beliefs; that's the service that this site provides. – Eric Lippert Apr 30 '19 at 17:26
  • 3
    @Servy: When someone believes that a thing *shouldn't be possible*, and asks *why is it possible?*, the root problem isn't that the *mechanism* is poorly understood -- though I agree that this is an opportunity to explain the mechanism. The root problem is the incorrect belief about what *should or should not be possible*. That's the problem to address! Now, if the original poster, as they say, does *not* actually have this false belief then the question should be more clearly stated. – Eric Lippert Apr 30 '19 at 17:28
  • @EricLippert What wasn't clearly stated about the problem to you? They clearly stated that they thought that the results of the query would be eagerly computed and stored for later iteration, and were surprised that they weren't. They were informed that that's not what happens, and that the values aren't computed until right before they're needed, which they accepted as the answer, indicating they did in fact not know that. They never indicated that they thought execution was deferred yet there was some other mechanism to prevent iteration of a collection that changed. – Servy Apr 30 '19 at 17:37
  • @EricLippert Their statement of their expected results *makes their question very clear*. They're not asking why you're able to iterate a collection that had changed, they're wondering why *the results are observing the change, rather than observing an earlier snapshot*. – Servy Apr 30 '19 at 17:38
  • 4
    @Servy: All of that was clear to me, as was the question, which was "Why am I able to edit a LINQ list while iterating over it?", and as was the incorrect belief "it's my understanding that in C#, you aren't supposed to be able to edit the list you're iterating over", both of which I addressed in my answer. I'm not sure why you're still going on about this; it's all very clear to me. I have some experience with LINQ and how people misunderstand it, being the person in the world who has been disabusing people's misunderstandings the longest. – Eric Lippert Apr 30 '19 at 17:40
  • 1
    Claiming that you're right because of how much experience you have, rather than by presenting facts and evidence to support your position, is not particularly convincing. I know you have lots of experience disabusing people's misunderstandings of LINQ, and lots of other people have lots of experience doing so as well. You know as well as I do that it's a common problem that people don't realize that LINQ methods return queries rather than the results of those queries. It's not exactly a fringe misunderstanding. – Servy Apr 30 '19 at 18:23
  • 3
    And it's one that I addressed in my answer, so I'm not sure what is actionable about your critique. The core problem with the other answers is that they addressed *only* that concern, rather than focusing on the crux of the issue, which is that the original poster appeared to have an incorrect belief about the possibility of editing an enumerable while it was being iterated. The person claiming that there's some aspect of this that I don't understand is *you*, not *me*; I'm not saying that I'm automatically right; I'm saying that you have every reason to know that I understand the question. – Eric Lippert Apr 30 '19 at 18:28
  • 6
    So far this answer, which addresses a central point not addressed by the other answers, has been critiqued for its tone -- which is factual and clear -- and has been critiqued on the basis of an unfounded suggestion that I don't understand the question. The only valid criticisms I've seen so far is its lack of concision, which I thoroughly agree with, and that it addresses a false belief not actually held by the original poster -- in which case, the question should have been more clear. I'm only going to respond to critiques based on the accuracy of the answer. – Eric Lippert Apr 30 '19 at 18:31
  • 2
    After reread your answer, I decided to extend my answer for completness and as a side note, while your answer repeatedly emphasizes that this is a bad practice, it does not adress why it is so and neither does it answer why he is not outputing 4 b's in his code example. – Raul May 02 '19 at 06:49
  • 1
    @RaulSebastian: First off, I like your revised answer a lot; it really digs into the "how it goes wrong" aspect in this specific case. To address your point: the reason *why* it is a bad practice is because, as we've seen, *pretty much anything can happen*, and therefore *you can get a program crash, or, even worse, unexpected results*. Your answer does a good job of showing how we can get unexpected results, but we could show entirely different results by making only small changes to the query. Imagine what happens if the query contains an order by or a group by clause, for example! – Eric Lippert May 02 '19 at 13:28
  • 1
    @RaulSebastian: That is, suppose the code written by the OP sorted the array before running the query and modified the query in the loop; you would again observe both the modifications being observed by the filter predicate, and the results might no longer be sorted. But if you added an order by clause before the where, LINQ would make a copy of the sorted version and enumerate that. It seems like moving the sort into the query ought to be a refactoring that preserves the program semantics, but in a world where you modify the collection in the loop, it is not. – Eric Lippert May 02 '19 at 13:33
  • 1
    @RaulSebastian: I think our two answers are good "complements" after your edit. This also illustrates a point that I have made many times: "why" questions are hard to interpret! I interpreted the "why" question here as asking "what fundamental language principles apply to this scenario?" and the principle is that modifying a collection as you enumerate it can produce unexpected results and is therefore a bad practice. Your interpretation of the "why" question is "what mechanism explains the behavior in this specific case?" Both are reasonable interpretations; the question is ambiguous. – Eric Lippert May 02 '19 at 13:38
  • 1
    Agree. Your answer pointed out the main issue in his question and a closer look to the question shows that he actually asks multiple things in context of his expectations. – Raul May 02 '19 at 15:20
  • @EricLippert: Is it still considered bad practice to update properties of an object inside of a foreach of an enumberable if you force it to execute up front with a ToList()? foreach(var o in e.Where(i=>i.SomeCondition).ToList()){o.SomeCondition= !o.SomeCondtion;} – Learning2Code Jun 10 '20 at 15:31
  • @Learning2Code: I deleted an earlier comment because it did not address the central point of your question -- which probably should be asked as a question, rather than addressed in a comment. – Eric Lippert Jun 10 '20 at 20:53
  • @Learning2Code: I think your question is: "if we modify a property of an object in a collection that is being queried, and the query reads that property, is it in general safer to do two steps: *first*, accumulate the list of objects to be changed, and *second* execute the changes?" Yes, that's a good idea. – Eric Lippert Jun 10 '20 at 20:55
  • @Learning2Code: However, I note that the comments to this answer indicate that there is some confusion about whether the question is about *modifying the objects in a collection as the collection is iterated*, and *modifying the collection itself as it is iterated*. Both are potentially *problematic* because of the potential of a mutation causing an enumeration to change in ways you did not anticipate. However, the latter is *wrong*; modifying a collection as you iterate it is an invalid operation that may or may not cause an exception. – Eric Lippert Jun 10 '20 at 20:57
10

The explanation to your first question, why your LINQ query re-runs every time it's iterated over is because of Linq's deferred execution.

This line just declares the linq exrpession and does not execute it:

var linqLIST = aArray.Where(x => x == "a");

and this is where it gets executed:

foreach (var arrItem in aArray)

and

Console.WriteLine(linqList.Count());

An explict call ToList() would run the Linq expression immediately. Use it like this:

var linqList = aArray.Where(x => x == "a").ToList();

Regarding the edited question:

Of course, the Linq expression is evaluated in every foreach iteration. The issue is not the Count(), instead every call to the LINQ expression re-evaluates it. As mentioned above, enumerate it to a List and iterate over the list.

Late edit:

Concerning @Eric Lippert's critique, I will also refer and go into detail for the rest of the OP's questions.

//Why does this only print out 2 a's and 2 b's, rather than 4 b's?

In the first loop iteration i = 3, so after aArray[3] = "b"; your array will look like this:

{ "a", "a", "a", "b" }

In the second loop iteration i(--) has now the value 2 and after executing aArray[i] = "b"; your array will be:

{ "a", "a", "b", "b" }

At this point, there are still a's in your array but the LINQ query returns IEnumerator.MoveNext() == false and as such the loop reaches its exit condition because the IEnumerator internally used, now reaches the third position in the index of the array and as the LINQ is re-evaluated it doesn't match the where x == "a" condition any more.

Why am I able to change what I'm looping over as I'm looping over it?

You are able to do so because the build in code analyser in Visual Studio is not detecting that you modify the collection within the loop. At runtime the array is modified, changing the outcome of the LINQ query but there is no handling in the implementation of the array iterator so no exception is thrown. This missing handling seems by design, as arrays are of fixed size oposed to lists where such an exception is thrown at runtime.

Consider following example code which should be equivalent with your initial code example (before edit):

using System;
using System.Linq;

namespace MyTest {
    class Program {
        static void Main () {
            var aArray = new string[] {
                "a", "a", "a", "a"
            };
            var iterationList = aArray.Where(x => x == "a").ToList();
            foreach (var item in iterationList)
            {
                var index = iterationList.IndexOf(item);
                iterationList.Remove(item);
                iterationList.Insert(index, "b");
            }
            foreach (var arrItem in aArray)
            {
                Console.WriteLine(arrItem);
            }
            Console.ReadKey();
        }
    }
}

This code will compile and iterate the loop once before throwing an System.InvalidOperationException with the message:

Collection was modified; enumeration operation may not execute.

Now the reason why the List implementation throws this error while enumerating it, is because it follows a basic concept: For and Foreach are iterative control flow statements that need to be deterministic at runtime. Furthermore the Foreach statement is a C# specific implementation of the iterator pattern, which defines an algorithm that implies sequential traversal and as such it would not change within the execution. Thus the List implementation throws an exception when you modify the collection while enumerating it.

You found one of the ways to modify a loop while iterating it and re-eveluating it in each iteration. This is a bad design choice because you might run into an infinite loop if the LINQ expression keeps changing the results and never meets an exit condition for the loop. This will make it hard to debug and will not be obvious when reading the code.

In contrast there is the while control flow statement which is a conditional construct and is ment to be non-deterministic at runtime, having a specific exit condition that is expected to change while execution. Consider this rewrite base on your example:

using System;
using System.Linq;

namespace MyTest {
    class Program {
        static void Main () {
            var aArray = new string[] {
                "a", "a", "a", "a"
            };
            bool arrayHasACondition(string x) => x == "a";
            while (aArray.Any(arrayHasACondition))
            {
                var index = Array.FindIndex(aArray, arrayHasACondition);
                aArray[index] = "b";
            }
            foreach (var arrItem in aArray)
            {
                Console.WriteLine(arrItem); //Why does this only print out 2 a's and 2 b's, rather than 4 b's?
            }
            Console.ReadKey();
        }
    }
}

I hope this should outline the technical background and explain your false expectations.

Raul
  • 2,745
  • 1
  • 23
  • 39
  • 1
    Is it not executed at the beginning of the foreach loop? If you were to remove the Count all together, the foreach loop only has 2 iterations before it completes. – Brosto Apr 30 '19 at 15:03
  • It's executed every time you use it. Executing it once does not cache/memoize the results into a list (this is what `ToList` and `ToArray` are for). – nlawalker Apr 30 '19 at 15:20
  • This post I believe provides a bit more explanation. https://stackoverflow.com/questions/16946207/does-foreach-cause-repeated-linq-execution – Brosto Apr 30 '19 at 15:33
  • @Brosto What I meant is that the initial declaration is not executed and of course the foreach as any other call to the linq expression leads to an evaluation. Maybe I had difficulties in expressing – Raul Apr 30 '19 at 15:39
  • Wow, thanks @EricLippert, I didn't see the edit in the answer, and misread some of the context in the comments. An enumerable is *not* evaluated anew in every iteration of a foreach. – nlawalker Apr 30 '19 at 15:45
  • @nlawalker: A LINQ enumerable is re-evaluated every time you do a `foreach` on it; if you have a query over a collection, and the collection changes, then the query results can change. That's not the issue. The issue is that the original poster has an incorrect belief about what the rules are. – Eric Lippert Apr 30 '19 at 15:47
  • @RaulSebastian - Thanks for the explanation, I learned something new today. +1 – Brosto Apr 30 '19 at 15:58
  • 2
    @EricLippert the answer just explains the behavior of the code. Your statement is correct and explain his confusion, I did not judge his code. There are quite some tricks to make C# behave unexpected. If he would have used a List and tried to manipulate the collection within the iteration, it would have been intercepted before compilation, but as you stated there are several logical issues that can’t be analyzed. Either way he should now be able to understand the issue – Raul Apr 30 '19 at 16:09
3

Enumerable.Where returns an instance that represents a query definition. When it is enumerated*, the query is evaluted. foreach allows you to work with each item at the time it is found by the query. The query is deferred, but it also pause-able/resume-able, by the enumeration mechanisms.

var aArray = new string[] { "a", "a", "a", "a" };
var i = 3;
var linqObj = aArray.Where(x => x == "a");
foreach (var item in linqObj )
{
  aArray[i] = "b";
  i--;
}
  • At the foreach loop, linqObj is enumerated* and the query is started.
  • The first item is examined and a match is found. The query is paused.
  • The loop body happens: item="a", aArray[3]="b", i=2
  • Back to the foreach loop, the query is resumed.
  • The second item is examined and a match is found. The query is paused.
  • The loop body happens: item="a", aArray[2]="b", i=2
  • Back to the foreach loop, the query is resumed.
  • The third item is examined and is "b", not a match.
  • The fourth item is examined and is "b", not a match.
  • The loop exits and the query concludes.

Note: is enumerated* : this means GetEnumerator and MoveNext are called. This does not mean that the query is fully evaluated and results held in a snapshot.

For further understanding, read up on yield return and how to write a method that uses that language feature. If you do this, you'll understand what you need in order to write Enumerable.Where

Amy B
  • 108,202
  • 21
  • 135
  • 185
2

IEnumerable in c# is lazy. This means whenever you force it to evaluate you get the result. In your case Count() forces the linqLIST to evaluate every time you call it. by the way, linqLIST is not a list right now

ilkerkaran
  • 4,214
  • 3
  • 27
  • 42
  • I can take the `.Count()` function out of the equation and I still have the same underlying issue with my list changing. Updated the code to reflect this – Joosh1337 Apr 30 '19 at 15:25
2

You could upgrade the «avoid side-effects while enumerating an array» advice to a requirement, by utilizing the extension method below:

private static IEnumerable<T> DontMessWithMe<T>(this T[] source)
{
    var copy = source.ToArray();
    return source.Zip(copy, (x, y) =>
    {
        if (!EqualityComparer<T>.Default.Equals(x, y))
            throw new InvalidOperationException(
            "Array was modified; enumeration operation may not execute.");
        return x;
    });
}

Now chain this method to your query and watch what happens.

var linqObj = aArray.DontMessWithMe().Where(x => x == "a");

Of course this comes with a cost. Now every time you enumerate the array, a copy is created. This is why I don't expect that anyone will use this extension, ever!

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    you get my upvote for the (funny named) solution and because you mentioned the overhead – Raul May 02 '19 at 09:19