Reputation: 18600
I'm enumerating over a collection that implements IList, and during the enumeration I am modifying the collection. I get the error, "Collection was modified; enumeration operation may not execute."
I want to know why this error occurs when modifying a item in the collection during iteration. I've already converted my foreach loop to a for loop, but I want to know the 'details' on why this error occurs.
Upvotes: 15
Views: 17441
Reputation: 310792
Internally, most of the base collection classes maintain a version number. Whenever you add, remove, reorder, etc the collection, this version number is incremented.
When you start enumerating, a snapshot of the version number is taken. Each time around the loop, this version number is compared against the collection's, and if they are different then this exception is thrown.
Whilst it would be possible to implement IList
so that it could correctly deal with changes to the collection made within the foreach loop (by having the enumerator track the collection's changes), it is a much harder task to correctly deal with changes made to the collection by other threads during the enumeration. So this exception exists to help identify vulnerabilities in your code, and to provide some kind of early warning about any potential instabilities brought about by manipulation from other threads.
Upvotes: 11
Reputation: 48710
While others have described why it's invalid behaviour, they haven't offered up a solution to your problem. While you may not need a solution, I'm going to provide it anyway.
If you want to observe a collection that is different to the collection that you are iterating over, you must return a new collection.
For instance..
IEnumerable<int> sequence = Enumerable.Range(0, 30);
IEnumerable<int> newSequence = new List<int>();
foreach (var item in sequence) {
if (item < 20) newSequence.Add(item);
}
// now work with newSequence
This is how you should be 'modifying' collections. LINQ takes this approach when you want to modify a sequence. For instance:
var newSequence = sequence.Where(item => item < 20); // returns new sequence
Upvotes: 5
Reputation: 180874
The real technical reason in this scenario is because Lists contain a private member called "version". Every modification - Add/Remove - increments the Version. The Enumerator that GetEnumerator returns stores the version at the moment it is created and checks the Version every time Next is called - if it's not equal, it throws an exception.
This is true for the builtin List<T>
class and possibly for other collections, so if you implement your own IList (rather than just subclassing/using a built in collection internally) then you may be able to work around that, but generally, enumeration and mofication should be done in a backwards for-loop or using a secondary List, depending on the scenario.
Note that modifying an item is perfectly fine, only Add/Remove is not.
Upvotes: 2
Reputation: 4555
The reason you see this is The IEnumerator returned by the underlying collection may expose the current property as read-only. Generally, you should avoid changes to collections(in fact most cases you will not even be able to change the collection) using for-each.
Upvotes: 0
Reputation: 23551
I assume the reason is that the state of the enumerator object is related to the state of the collection. For example a list enumerator would have an int field to store the index of the current element. However if you remove an element from the list you are shifting all the indexes after the element left by one. At this point the enumerator would skip an object thus manifesting wrong behaviour. Making the enumerator valid for all possible cases would require complex logic and can hurt performance for the most common case (not changing the collection). I believe this is why the designers of the collections in .NET decided that they should just throw an exception when the enumerator is in invald state instead of trying to fix it.
Upvotes: 1
Reputation: 887195
This is the specified behavior:
Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection.
This limitation makes enumerators simpler to implement.
Note that some collections will (incorrectly) allow you to enumerate while modifying the collection.
Upvotes: 0
Reputation: 168928
From the IEnumerable documentation:
An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.
I believe the reasoning for this decision is that it cannot be guaranteed that all types of collections can sustain modification and still preserve an enumerator state. Consider a linked list -- if you remove a node and an enumerator is currently on that node, the node reference may be its only state. And once that node is removed, the "next node" reference will be set to null
, effectively invalidating the enumerator state and preventing further enumeration.
Since some collection implementations would have serious trouble with this kind of situation, it was decided to make this part of the IEnumerable interface contract. Allowing modification in some situations and not others would be horribly confusing. In addition, this would mean that existing code that might rely on modifying a collection while enumerating it would have serious problems when the collection implementation is changed. So making the behavior consistent across all enumerables is preferable.
Upvotes: 17