本周继续偷懒打个支线任务。图片这是一个算是比较古老的东西,但是实际工作中可能并不会想到。对于List,使用Foreach获取里面的数据是非常普遍的操作。如果我们在循环中改变List,会怎么样?
于是,我们运行下面这个代码。
static void Main(string[] args)
{
List<int> list = new List<int>() { 0,1,2,3,4,5,6,7,8,9};
list.ForEach(l =>
{
Console.WriteLine(l);
if(l == 5)
{
list.RemoveAt(0);
}
});
}
然后,引发了一个异常:
System.InvalidOperationException:“Collection was modified; enumeration operation may not execute.”

这是为什么呢,显然Foreach这个方法里面有点东西。
在List中,Foreach的方法代码如下:
[__DynamicallyInvokable]
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
int version = this._version;
int num = 0;
while (num < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5))
{
action(this._items[num]);
num++;
}
if (version != this._version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
}
里面关注这两个部分,这个异常是下面的if判断抛出来的。TargetsAtLeast_Desktop_V4_5这个不用太过关心,这年头了,版本比这个低,你该想想自己的问题。

那么就是说两个version不相等了。Foreach初始化的时候是相同的,那么是哪里改变的呢?我们回到自己的代码,在循环里面,我们进行了Remove。
list.RemoveAt(0);
我们看下RemoveAt的代码
[__DynamicallyInvokable]
public void RemoveAt(int index)
{
if (index >= this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException();
}
this._size--;
if (index < this._size)
{
Array.Copy(this._items, index + 1, this._items, index, this._size - index);
}
this._items[this._size] = default(T);
this._version++;
}
最后这里,看到了吗,当我们修改了List,Version就会递增。
this._version++;
包括但不限于Remove系列方法,Clear,Insert等一系列修改List的操作,都会导致version改变,从而Foreach报错。