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>对象的本质是数组,而不是链表。
在执行的时候会跑出如下异常:
为什么会这样子呢,我们知道,foreach语句在执行的时候是通过被遍历对象(这里是List<T>对象)的枚举器来实现的。
我们首先关注List<T>对象枚举器的实现细节:
{
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。
相关文章推荐
- C++ list<list<int> >类型的对象遍历
- JavaSE8基础 增强型for循环遍历 为null的ArrayList<String>时会抛出NullPointerException异常
- 一个 ArrayList 对象 aList 中存有若干个字符串元素,现欲遍历该 ArrayList 对象,删除其中 所有值为"abc"的字符串元素,请用代码实现。
- List<?> list= new ArrayList<?>为什么这样写 接口引用指向实现类的对象
- Collections.copy(List<? super T> dest, List<? extends T> src) 抛出异常 Source does not fit in dest 原因剖析
- <c:foreach>同时遍历两个List的方法,List长度最好一致的
- C# List<Apple>删除指定的对象
- 用<forEach>遍历list集合时,提示我找不到对象的属性
- <c:foreach>同时遍历两个list的方法
- scala调用java的方法,返回了一个对象链表List<Student>,在scala中遍历该链表获取指定Student的名字name
- fail-fast机制(遍历的同时删除List中的对象)
- <c:foreach>同时遍历两个List的方法
- ajax遍历List<对象>的三种方式
- jsp中使用<c:foreach>同时遍历两个list集合
- EL JSTL <c:forEach>遍历时同时获取对象和序号,<c:choose>多条件判断,el标签条件
- <c:foreach>同时遍历两个List的方法
- 通过Struts标签和JSTL标签来遍历List<Map<String,String>>格式的数据
- 将List<Object[]>数组对象转换成List<HashMap<String, Object>>集合对象
- S动态添加与删除select中的Option对象 2009年09月23日 15:26 <html> <head> <meta http-equiv="Content-Type" content="t