The collection has been modified; enumeration operation cannot be performed - why?

I enumerate a collection that implements IList, and during enumeration I modify the collection. I get an error: "The collection has been modified, the enumeration operation cannot be performed."

I want to know why this error occurs when changing an item in a collection during iteration. I have already converted the foreach loop to a for loop, but I want to know the β€œdetails” about why this error occurs.

+12
c # enumeration
Dec 27 2018-10-12T00:
source share
7 answers

From the IEnumerable documentation :

The enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, changing or deleting elements, the enumerator is irrevocably canceled and its behavior is undefined.

I believe that the rationale for this solution is that it cannot be guaranteed that all types of collections can support modification and maintain the state of the enumerator. Consider a linked list - if you remove the node, and there is currently an enumerator on it, the node link may be its only state. And as soon as this node is deleted, the link "next node" will be set to null , which will actually invalidate the state of the enumerator and prevent further enumeration.

Since some implementations of collections will have serious problems with this situation, it was decided to make this part of the IEnumerable interface contract. Allowing modification in some situations, and not others, would be terribly confusing. In addition, this will mean that existing code that can rely on modifying the collection when listing it will have serious problems when changing the implementation of the collection. Therefore, preferred behavior is compatible with all enumerated.

+17
Dec 27 2018-10-12T00:
source share

how

Inside, most base set classes support version number. Whenever you add, delete, reorder, etc. Collection, this version number is incremented.

When you start the listing, a snapshot of the version number is taken. Each time around the loop, this version number is compared with the collection, and if they are different, this exception is thrown.

Why

While it would be possible to implement IList so that it can correctly handle changes to collections made in the foreach loop (given that the enumerator keeps track of changes to the collection), the task is much more difficult to handle the changes made to the collection by other threads during the enumeration . Thus, this exception exists to help identify vulnerabilities in your code and provide an early warning of any potential volatility caused by manipulations with other threads.

+10
Dec 27 2018-10-12T00:
source share

While others described why this behavior is invalid, they did not offer a solution to your problem. Although you may not need a solution, I will provide it anyway.

If you want to watch a collection that is different from the collection that you are repeating, you must return a new collection.

For example..

 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 

Here's how you should β€œmodify” collections. LINQ takes this approach if you want to change the sequence. For example:

 var newSequence = sequence.Where(item => item < 20); // returns new sequence 
+4
Dec 27 '10 at 1:59
source share

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 must have an int field for storing the index of the current element. However, if you remove an item from the list, you move all the indexes after the item remains on one. At this point, the enumerator skips the object, thereby exhibiting incorrect behavior. Making an enumerator valid for all possible cases will require complex logic and may damage performance for the most common case (without changing the collection). I believe that is why collection designers in .NET decided that they should just throw an exception when the enumerator is in invald state and not trying to fix it.

+1
Dec 27 '10 at 1:08
source share

The real technical reason in this scenario is that lists contain a private member called the "version". Each modification - Add / Remove - increases the version. The enumerator that returns GetEnumerator saves the version at the time of its creation and checks the version each time "Next" is called - if it is not equal, it throws an exception.

This is true for the List<T> inline class, and possibly for other collections, so if you implement your own IList (and not just subclass / use the inline collection inside), then you can get around this, but usually listing and mofication must be done in reverse for the loop or using a secondary list depending on the scenario.

Note that changing an item is fine, only adding / removing is not performed.

+1
Dec 27 2018-10-12T00:
source share

The reason you see this: The IEnumerator returned by the base assembly may display the current property as read-only. As a rule, you should avoid changing collections (in fact, most of the times you cannot even change the collection), using for each.

0
Dec 27 '10 at 0:59
source share

This is indicated :

Enumerators can be used to read data in a collection, but they cannot be used to modify the underlying collection.

This restriction simplifies the implementation of counters. Please note that some collections (incorrectly) will allow you to list when the collection changes.

0
Dec 27 '10 at 0:59
source share



All Articles