您的位置:首页 > 其它

IL Discovery 系列三 《为什么在遍历List<T>对象时同时删除其中项会抛出异常》

2011-08-29 12:58 597 查看
对于如下简单的代码:

staticvoidILDiscoveryListDelete() { List<int>list=newList<int> { 1,2,3,4, }; foreach(variteminlist) { list.Remove(item); } }

在执行的时候会跑出如下异常:





为什么会这样子呢,我们知道,foreach语句在执行的时候是通过被遍历对象(这里是List<T>对象)的枚举器来实现的。

我们首先关注List<T>对象枚举器的实现细节:

.methodpublichidebysignewslotvirtualfinalinstanceboolMoveNext()cilmanaged { .maxstack3 .localsinit( [0]classSystem.Collections.Generic.List`1<!T>list) L_0000:ldarg.0 L_0001:ldfldclassSystem.Collections.Generic.List`1<!0>System.Collections.Generic.List`1/Enumerator<!T>::list L_0006:stloc.0 L_0007:ldarg.0 L_0008:ldfldint32System.Collections.Generic.List`1/Enumerator<!T>::version L_000d:ldloc.0 L_000e:ldfldint32System.Collections.Generic.List`1<!T>::_version L_0013:bne.un.sL_004a L_0015:ldarg.0 L_0016:ldfldint32System.Collections.Generic.List`1/Enumerator<!T>::index L_001b:ldloc.0 L_001c:ldfldint32System.Collections.Generic.List`1<!T>::_size L_0021:bge.un.sL_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:ldfldint32System.Collections.Generic.List`1/Enumerator<!T>::index L_0030:ldelem.any!T L_0035:stfld!0System.Collections.Generic.List`1/Enumerator<!T>::current L_003a:ldarg.0 L_003b:dup L_003c:ldfldint32System.Collections.Generic.List`1/Enumerator<!T>::index L_0041:ldc.i4.1 L_0042:add L_0043:stfldint32System.Collections.Generic.List`1/Enumerator<!T>::index L_0048:ldc.i4.1 L_0049:ret L_004a:ldarg.0 L_004b:callinstanceboolSystem.Collections.Generic.List`1/Enumerator<!T>::MoveNextRare() L_0050:ret } 这里没有必要阅读IL,通过Reflector,代码如下:
publicboolMoveNext() { List<T>list=this.list; if((this.version==list._version)&&(this.index<list._size)) { this.current=list._items[this.index]; this.index++; returntrue; } returnthis.MoveNextRare(); }
我们看到,在每次MoveNext的时候,List会检查当前枚举器对象的版本和list的版本是否一致,如果不一致就执行this.MoveNextRare()方法。

我们再关注一下Remove方法:
publicvoidRemoveAt(intindex)
{
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>对象的本质是数组,而不是链表。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐