对于如下简单的代码:
static void ILDiscoveryListDelete()
{
List<int> list = new List<int>
{
1,2,3,4,
};
foreach (var item in list)
{
list.Remove(item);
}
}
在执行的时候会跑出如下异常:
为什么会这样子呢,我们知道,foreach语句在执行的时候是通过被遍历对象(这里是List<T>对象)的枚举器来实现的。
我们首先关注List<T>对象枚举器的实现细节:
.method public hidebysig newslot virtual final instance bool MoveNext() cil managed
{
.maxstack 3
.locals init (
[0] class System.Collections.Generic.List`1<!T> list)
L_0000: ldarg.0
L_0001: ldfld class System.Collections.Generic.List`1<!0> System.Collections.Generic.List`1/Enumerator<!T>::list
L_0006: stloc.0
L_0007: ldarg.0
L_0008: ldfld int32 System.Collections.Generic.List`1/Enumerator<!T>::version
L_000d: ldloc.0
L_000e: ldfld int32 System.Collections.Generic.List`1<!T>::_version
L_0013: bne.un.s L_004a
L_0015: ldarg.0
L_0016: ldfld int32 System.Collections.Generic.List`1/Enumerator<!T>::index
L_001b: ldloc.0
L_001c: ldfld int32 System.Collections.Generic.List`1<!T>::_size
L_0021: bge.un.s L_004a
L_0023: ldarg.0
L_0024: ldloc.0
L_0025: ldfld !0[] System.Collections.Generic.List`1<!T>::_items
L_002a: ldarg.0
L_002b: ldfld int32 System.Collections.Generic.List`1/Enumerator<!T>::index
L_0030: ldelem.any !T
L_0035: stfld !0 System.Collections.Generic.List`1/Enumerator<!T>::current
L_003a: ldarg.0
L_003b: dup
L_003c: ldfld int32 System.Collections.Generic.List`1/Enumerator<!T>::index
L_0041: ldc.i4.1
L_0042: add
L_0043: stfld int32 System.Collections.Generic.List`1/Enumerator<!T>::index
L_0048: ldc.i4.1
L_0049: ret
L_004a: ldarg.0
L_004b: call instance bool System.Collections.Generic.List`1/Enumerator<!T>::MoveNextRare()
L_0050: ret
}
这里没有必要阅读IL,通过Reflector,代码如下:
public bool MoveNext()
{
List<T> list = this.list;
if ((this.version == list._version) && (this.index < list._size))
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
我们看到,在每次MoveNext的时候,List会检查当前枚举器对象的版本和list的版本是否一致,如果不一致就执行this.MoveNextRare()方法。
我们再关注一下Remove方法:
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++;
}
从该方法,没进行一次Remove操作,list的_version字段会自增1。
到这里,基本上咱们就该明白为什么最上面的代码执行不下去了。
结论:
-
在遍历的同时,修改List对象,这样会抛出异常,这是因为List<T>对象和List<T>::Enumerator对象各自维护了一个版本字段,如果发现这两个版本字段不一致,就会中止遍历,抛出异常。
-
List<T>对象的本质是数组,而不是链表。