关于C#:为什么list<>.foreach允许修改其列表?

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

如果我使用:

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

foreach中的Add抛出了一个invalidOperationException(集合已修改;枚举操作可能无法执行),我认为这是合乎逻辑的,因为我们正在脚下拉起地毯。

但是,如果我使用:

1
2
3
4
5
6
var strings = new List<string> {"sample" };
strings.ForEach(s =>
  {
    Console.WriteLine(s);
    strings.Add(s +"!");
  });

它立即通过循环射击自己的脚,直到抛出一个记忆例外。

这对我来说是一个惊喜,正如我一直认为的那样,list.foreach或者只是foreach或者for的包装。有没有人能解释这种行为的方式和原因?

(由foreach循环导入,以获取无休止重复的通用列表)


这是因为ForEach方法不使用枚举器,它使用for循环遍历项:

1
2
3
4
5
6
7
8
9
10
11
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]);
    }
}

(使用justdecompile获取的代码)

由于不使用枚举器,因此它从不检查列表是否已更改,并且由于每次迭代都增加了_size,因此从未达到for循环的结束条件。


List.ForEach是通过for内部实现的,因此不使用枚举器,允许修改集合。


因为在内部附加到列表类的foreach使用直接附加到其内部成员的for循环——您可以通过下载.NET框架的源代码来看到。

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

其中,as-foreach循环首先是编译器优化,但还必须作为观察者对集合进行操作——因此,如果修改了集合,则会引发异常。


我们知道这个问题,这是最初写的时候的一个疏忽。不幸的是,我们无法更改它,因为它现在会阻止以前工作的代码运行:

1
2
3
4
5
6
7
8
9
        var list = new List<string>();
        list.Add("Foo");
        list.Add("Bar");

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

正如EricLippert指出的那样,这种方法本身的有用性是值得怀疑的,因此我们没有将它包括在.NET中用于Metro风格的应用程序(即Windows8应用程序)。

David Kean(BCL团队)