Why does List<T>.ForEach allow its list to be modified?
如果我使用:
1 2 3 4 5 6 | var strings = new List<string> {"sample" }; foreach (string s in strings) { Console.WriteLine(s); strings.Add(s +"!"); } |
但是,如果我使用:
1 2 3 4 5 6 | var strings = new List<string> {"sample" }; strings.ForEach(s => { Console.WriteLine(s); strings.Add(s +"!"); }); |
它立即通过循环射击自己的脚,直到抛出一个记忆例外。
这对我来说是一个惊喜,正如我一直认为的那样,list.foreach或者只是
(由foreach循环导入,以获取无休止重复的通用列表)
这是因为
1 2 3 4 5 6 7 8 9 10 11 | 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获取的代码)
由于不使用枚举器,因此它从不检查列表是否已更改,并且由于每次迭代都增加了
因为在内部附加到列表类的foreach使用直接附加到其内部成员的for循环——您可以通过下载.NET框架的源代码来看到。
http://referencesource.microsoft.com/netframework.aspx
其中,as-foreach循环首先是编译器优化,但还必须作为观察者对集合进行操作——因此,如果修改了集合,则会引发异常。
我们知道这个问题,这是最初写的时候的一个疏忽。不幸的是,我们无法更改它,因为它现在会阻止以前工作的代码运行:
1 2 3 4 5 6 7 8 9 | var list = new List<string>(); list.Add("Foo"); list.Add("Bar"); list.ForEach((item) => { if(item=="Foo") list.Remove(item); }); |
正如EricLippert指出的那样,这种方法本身的有用性是值得怀疑的,因此我们没有将它包括在.NET中用于Metro风格的应用程序(即Windows8应用程序)。
David Kean(BCL团队)