这话题已经被提过很多次,Java和.NET社区都应该很熟悉这点了,但总是会见到有人犯这个错误……
举个糟糕的代码为例:
代码中的两个循环用LINQ的扩展方法或者用C#内建的foreach语句效果都一样。很容易看出,第11行改变了dummyList的内容。但比较隐蔽的是,这个改变是通过IEnumerable<T>.GetEnumerator()得到一个IEnumerator<T>之后来做的。因为是通过enumerator(C++和Java社区习惯称之为iterator)来做修改,我们无法保证一个集合被修改后,先前得到的enumerator对象还能正确遍历集合。上面的例子里会看到“把表里的每个元素都删除”后,表里还剩下2和4。由于集合内容发生了变化,它们正好被跳过去了。
过程:
{ 1, 2, 3, 4, 5 }
删除位置0的元素,剩下{ 2, 3, 4, 5 }
删除位置1的元素,剩下{ 2, 4, 5 }
删除位置2的元素,剩下{ 2, 4 }
位置3超过表的长度,返回
想强调的就是,用LINQ的时候千万不要试图改变原本集合里的内容!
举个糟糕的代码为例:
using System;
using System.Collections.Generic;
using System.Linq;
namespace BadExample {
static class Program {
static void Main( string[ ] args ) {
var dummyList = new List<int> { 1, 2, 3, 4, 5 };
// try to remove all entries with ForEach (or foreach statement).
dummyList.ForEach( item => dummyList.Remove( item ) );
// dummyList should be empty now, but...
// we still see 2 and 4 in the list!
dummyList.ForEach( item => Console.WriteLine( item ) );
}
}
}
代码中的两个循环用LINQ的扩展方法或者用C#内建的foreach语句效果都一样。很容易看出,第11行改变了dummyList的内容。但比较隐蔽的是,这个改变是通过IEnumerable<T>.GetEnumerator()得到一个IEnumerator<T>之后来做的。因为是通过enumerator(C++和Java社区习惯称之为iterator)来做修改,我们无法保证一个集合被修改后,先前得到的enumerator对象还能正确遍历集合。上面的例子里会看到“把表里的每个元素都删除”后,表里还剩下2和4。由于集合内容发生了变化,它们正好被跳过去了。
过程:
{ 1, 2, 3, 4, 5 }
删除位置0的元素,剩下{ 2, 3, 4, 5 }
删除位置1的元素,剩下{ 2, 4, 5 }
删除位置2的元素,剩下{ 2, 4 }
位置3超过表的长度,返回
想强调的就是,用LINQ的时候千万不要试图改变原本集合里的内容!