如果我使用:
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只是foreach
或for
的包装。
有没有人对这种行为的方式和原因有解释?
答案 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团队)