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

如果我使用:

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

foreachAdd抛出一个InvalidOperationExceptionexception(集合被修改;枚举操作可能不会执行),我认为这是合乎逻辑的,因为我们正在从我们脚下扯下地毯。

但是,如果我使用:

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

它通过循环立即在脚中自我射击,直到抛出OutOfMemoryException。

这对我来说是一个惊喜,因为我一直认为List.ForEach不是为了foreach就是为了包装。
有没有人有解释如何和为什么这种行为?

(由ForEach循环为无限重复的generics列表启发)

这是因为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是通过内部实现的,所以它不使用枚举器,它允许修改集合。

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

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

作为一个foreach循环首先是一个编译器优化,但也必须作为观察者对集合进行操作 – 所以如果集合被修改,它会抛出一个exception。

我们知道这个问题,原来是这样写的。 不幸的是,我们不能改变它,因为它现在会阻止以前工作的代码运行:

  var list = new List<string>(); list.Add("Foo"); list.Add("Bar"); list.ForEach((item) => { if(item=="Foo") list.Remove(item); }); 

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

David Kean(BCL团队)