为啥 List<T>.ForEach 允许修改其列表?
Posted
技术标签:
【中文标题】为啥 List<T>.ForEach 允许修改其列表?【英文标题】:Why does List<T>.ForEach allow its list to be modified?为什么 List<T>.ForEach 允许修改其列表? 【发布时间】:2012-03-07 21:03:51 【问题描述】:如果我使用:
var strings = new List<string> "sample" ;
foreach (string s in strings)
Console.WriteLine(s);
strings.Add(s + "!");
foreach
中的 Add
抛出 InvalidOperationException(集合已修改;枚举操作可能无法执行),我认为这是合乎逻辑的,因为我们正在从脚下拉扯地毯。
但是,如果我使用:
var strings = new List<string> "sample" ;
strings.ForEach(s =>
Console.WriteLine(s);
strings.Add(s + "!");
);
它会迅速循环,直到抛出 OutOfMemoryException。
这对我来说是一个惊喜,因为我一直认为 List.ForEach 要么只是 foreach
或 for
的包装器。
有没有人解释这种行为的方式和原因?
(灵感来自ForEach loop for a Generic List repeated endlessly)
【问题讨论】:
我同意。这是 - 可疑的。我建议您将其发布在 microsoft connect 上并要求澄清。 "这对我来说是一个惊喜,因为我一直认为 List.ForEach 要么只是foreach
或 for
的包装器。"它仍然可以使用for
。您可以在 for
循环中执行相同的操作并生成相同的 OutOfMemoryException 作为结果。
这是基于我的问题:***.com/q/9311272/132239,感谢 SWeko 了解它的详细信息
【参考方案1】:
这是因为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
。
【讨论】:
是的,但是_size
是如何计算的?如果它只是预先计算的,那么对于我的示例,如果应该只运行一次。它显然以某种方式刷新了。
在Add方法刷新-> this._items[this._size++] = item;
@SWeko,不是计算的,每次添加或删除项目时都会更新。
List<T>
中有一个 _version
私有变量可以检测到这种情况,因为它会在更改列表本身的操作上更新。
您可以通过首先获取大小 (int theSize = this._size),然后在 for 循环中使用它来避免异常?【参考方案2】:
List<T>.ForEach
内部是通过for
实现的,所以它不使用枚举器,它允许修改集合。
【讨论】:
【参考方案3】:因为附加到 List 类的 ForEach 在内部使用了一个直接附加到其内部成员的 for 循环 - 您可以通过下载 .NET 框架的源代码来查看。
http://referencesource.microsoft.com/netframework.aspx
foreach 循环首先是编译器优化,但还必须作为观察者对集合进行操作——因此,如果集合被修改,它会引发异常。
【讨论】:
并回答@Thomas 帖子上关于它如何刷新的评论——调用 add 时会刷新内部成员,这就是它能够跟上变化的原因。如果您要在小于当前索引的索引处执行插入,您将永远不会对该项目进行操作,因为它已经迭代过该项目。但是,由于您要添加到最后,所以它可以工作。 是的,将Add
行更改为strings.Insert(0, s + "!")
只会打印出“样本”。奇怪的是,文档中根本没有提到这一点。
嗯,我认为微软意识到提供文档中存在的所有警告几乎是不可能的——所以他们现在提供了源代码。老实说,我发现一个更好的解决方案,但我发现的唯一问题是 WF 之类的产品更新速度不快—— 4.x WF 源代码仍然不可用。【参考方案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 应用程序)的 .NET 中。
大卫·基恩(BCL 团队)
【讨论】:
我知道这将是一个非常糟糕的重大变化,但它可能会以不明显的方式失败,这绝不是一件好事。我看不到使用 ForEach 方法优于简单 for 的场景(如果不需要修改原始列表,则为 foreach)以上是关于为啥 List<T>.ForEach 允许修改其列表?的主要内容,如果未能解决你的问题,请参考以下文章
List<T> 将 foreach 循环中的所有项目覆盖为最后一个值
mybatis 的 foreach 使用hashmap 传输 List ,hashmap中的List 不空,List中的元素为空,为啥呢。
为啥我在 List<List<T>> 中复制 List<T> 时存在依赖关系? [复制]