为什么 List < T > . ForEach 允许修改它的 List?

如果我使用:

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

foreach中的 Add抛出一个 InvalidOperationException (Collection 被修改; 枚举操作可能不会执行) ,我认为这是合乎逻辑的,因为我们正在从脚下拉地毯。

但是,如果我使用:

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

它立即循环射击自己的脚,直到抛出一个 OutOfMemory 异常。

这让我很惊讶,因为我一直认为。ForEach 只是 foreachfor的包装器。
有人能解释一下这种行为的原因吗?

(灵感来自 不断重复的泛型列表的每个循环)

2637 次浏览

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

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.

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)