2

When running this code:

var list = new List<string>
{
    "foo",
    "bar",
};

foreach (var l in list)
{
    Console.WriteLine(l);

    list.Add("bar");
}

An exception is thrown:

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

How does .NET know that the collection is modified while enumerator is iterating that collection? Is there a flag for this in the collection object?

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 1
    *How C# compiler knows* compiler does **not** know this fact, as it's a run-time detail, not a compile-time one :) –  Jan 10 '17 at 10:36

2 Answers2

7

A List<T> internally holds its "version" in an integer variable. Each modification to the list (Add, Remove, Sort, Clear, ...) increments this version by one.

Now the enumerator for a List<T>, when initialized, saves this version number. Upon every MoveNext(), i.e. on every iteration, it checks whether the list's version number still equals the saved version number.

This way it can detect when a list is modified between two iterations. Note this implementation causes a peculiar bug with integer overflow: Why this code throws 'Collection was modified', but when I iterate something before it, it doesn't?.

There also appears to be a bug regarding Sort(Comparison<T> comparison), which doesn't increment the version number. This code:

var list = new List<string>
{
    "foo",
    "bar",
};

foreach (var l in list)
{
    Console.WriteLine(l);
    list.Sort((x, y) => x.CompareTo(y));
}

Prints:

foo
foo
Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
5

Yes, the typical solution is a flag, version value etc., e.g. in case of List<T>

https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,cf7f4095e4de7646

it's a version

 private int _version;

E.g.

 public bool MoveNext() {
   ...
   return MoveNextRare()
 } 

 private bool MoveNextRare()
 {                
   if (version != list._version) {
     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
 }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215