Wednesday 23 March 2011

Collection was modified; enumeration...

Well, it's been years since I first stumbled upon this, but still today it sometimes gets my by surprise, I'm talking about this exception:
Collection was modified; enumeration operation may not execute

This happens when you're enumerating an IEnumerable, do some modification to it, and try to continue enumerating (do another MoveNext). Many times this happens in a foreach loop (that of course under the covers makes use of Enumerators...)

You can find good information on the net explaining why IEnumerables have been designed this way, and as pointed out there, MSDN says it clear:

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.

What is important here is that there's no way to force us to implement this part of the interface contract in our own classes, I mean, someone could program his own IEnumerables without following this rule, either deliberately of by mistake, but the expected behavior of a .Net IEnumerable should be that, and all should play by those rules.

How do the classes implementing IEnumerator and IEnumerable play together so that the former knows that the latter has been modified?
Well, sometime ago I had disassembled some code to take a peek, and found the same that is stated in the link above, the class implementing IEnumerable has a version field, that is increased by each method that involves a change (Add, Remove...). The class implementing IEnumerator also has a version field, that at creation time is filled with the version value in the IEnumerable. Each time MoveNext is executed it checks that the version values in the IEnumerator and the IEnumerable are the same, launching an exceptions if the condition is not met.

Taken from StackOverflow:

Internally, most of the base collection classes maintain a version number internally. 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.


Today I got this same exception with some code modifying a Dictionary while traversing its Keys collection, which at first surprised me a bit


foreach (string key in this.columnsMap.Keys)
this.columnsMap[key] = columns.IndexOf(key);


Well, I'm iterating the Keys collection, not the Dictionary itself, but of course, modifying the Dictionary should change the Keys collection invalidating it... and that's just what's happening.

Disassembling with ILSpy (I think this is going to be a great replacement for Reflector), we can see how the KeyCollection.Enumerator checks the Dictionary.version against its own version field:


No comments:

Post a Comment