简体   繁体   中英

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

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 ForEach loop for a Generic List repeated endlessly )

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.

List<T>.ForEach是通过for inside实现的,所以它不使用枚举器,它允许修改集合。

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.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM