对于如下简单的代码:
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>对象的本质是数组,而不是链表。