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
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.
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.
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.
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.
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
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.