为什么List<;T>;.ForEach允许修改其列表
本文关键字:修改 许修改 列表 ForEach lt List gt 为什么 | 更新日期: 2023-09-27 18:24:45
如果我使用:
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 + "!");
});
它立即通过循环来射中自己的脚,直到抛出OutOfMemoryException。
这让我很惊讶,因为我一直认为List.ForEach只是foreach
或for
的包装器
有人能解释这种行为的方式和原因吗?
(由ForEach循环对无休止重复的泛型列表进行修复)
这是因为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
在每次迭代时都会增加。
List<T>.ForEach
是通过内部的for
实现的,因此它不使用枚举器,并且允许修改集合。
因为连接到List类的ForEach在内部使用了一个直接连接到其内部成员的for循环——您可以通过下载.NET框架的源代码来看到这一点。
http://referencesource.microsoft.com/netframework.aspx
其中,作为foreach循环首先是编译器优化,但也必须作为观察者对集合进行操作——因此,如果修改了集合,就会引发异常。
我们知道这个问题,它在最初编写时是一个疏忽。不幸的是,我们无法更改它,因为它现在会阻止以前工作的代码运行:
var list = new List<string>();
list.Add("Foo");
list.Add("Bar");
list.ForEach((item) =>
{
if(item=="Foo")
list.Remove(item);
});
正如Eric Lippert所指出的,这种方法本身的有用性是值得怀疑的,所以我们没有将其包含在.NET中用于Metro风格的应用程序(即Windows 8应用程序)。
David Kean(BCL团队)