为什么List <t> .ForEach允许修改其列表?</t>

时间:2012-02-16 13:01:35

标签: c# list foreach

如果我使用:

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

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

但是,如果我使用:

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

它通过循环立即射入脚中直到它抛出OutOfMemoryException。

这对我来说是个惊喜,因为我一直以为List.ForEach只是foreachfor的包装。 有没有人对这种行为的方式和原因有解释?

(由ForEach loop for a Generic List repeated endlessly提供)

4 个答案:

答案 0 :(得分:68)

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

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获得的代码)

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

答案 1 :(得分:14)

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

答案 2 :(得分:6)

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

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

其中foreach循环首先是编译器优化,但也必须作为观察者对集合进行操作 - 因此如果集合被修改,则抛出异常。

答案 3 :(得分:4)

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

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

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

这个方法的用处本身是有问题的,因为Eric Lippert指出,所以我们没有将它包含在Metro风格的应用程序(即Windows 8应用程序)中。

David Kean(BCL团队)