Why does List<T>.ForEach allow its list to be modified?

C#ListForeach

C# Problem Overview


If I use:

var strings = new List<string> { "sample" };
foreach (string s in strings)
{
  Console.WriteLine(s);
  strings.Add(s + "!");
}

the Add in the foreach throws an InvalidOperationException (Collection was modified; enumeration operation may not execute), which I consider logical, since we are pulling the rug from under our feet.

However, if I use:

var strings = new List<string> { "sample" };
strings.ForEach(s =>
  {
    Console.WriteLine(s);
    strings.Add(s + "!");
  });

it promptly shoots itself in the foot by looping until it throws an OutOfMemoryException.

This comes as a suprise to me, as I always thought that List.ForEach was either just a wrapper for foreach or for for.
Does anyone have an explanation for the how and the why of this behavior?

(Inpired by https://stackoverflow.com/questions/9311272/foreach-loop-for-a-generic-list-repeated-endlessly)

C# Solutions


Solution 1 - C#

It's because the ForEach method doesn't use the enumerator, it loops through the items with a for loop:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

(code obtained with JustDecompile)

Since the enumerator is not used, it never checks if the list has changed, and the end condition of the for loop is never reached because _size is increased at every iteration.

Solution 2 - C#

List<T>.ForEach is implemented through for inside, so it does not use enumerator and it allows to modify the collection.

Solution 3 - C#

Because the ForEach attached to the List class internally uses a for loop that is directly attached to its internal members -- which you can see by downloading the source code for the .NET framework.

http://referencesource.microsoft.com/netframework.aspx

Where as a foreach loop is first and foremost a compiler optimization but also must operate against the collection as an observer -- so if the collection is modified it throws an exception.

Solution 4 - C#

We know about this issue, it was an oversight when it was originally written. Unfortunately, we can't change it because it would now prevent this previously working code from running:

        var list = new List<string>();
        list.Add("Foo");
        list.Add("Bar");

        list.ForEach((item) => 
        { 
            if(item=="Foo") 
                list.Remove(item); 
        });

The usefulness of this method itself is questionable as Eric Lippert pointed out, so we didn't include it for .NET for Metro style apps (ie Windows 8 apps).

David Kean (BCL Team)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSWekoView Question on Stackoverflow
Solution 1 - C#Thomas LevesqueView Answer on Stackoverflow
Solution 2 - C#Alexey RagaView Answer on Stackoverflow
Solution 3 - C#Mike PerrenoudView Answer on Stackoverflow
Solution 4 - C#David KeanView Answer on Stackoverflow