zoukankan      html  css  js  c++  java
  • IL Discovery 系列三 《为什么在遍历List<T>对象时同时删除其中项会抛出异常》

    对于如下简单的代码:

            static void ILDiscoveryListDelete()
            {
                List<int> list = new List<int>
                {
                    1,2,3,4,
                };
                foreach (var item in list)
                {
                    list.Remove(item);
                }
            }

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

    deleteListItem

    为什么会这样子呢,我们知道,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>对象的本质是数组,而不是链表。
  • 相关阅读:
    python-历史
    10-函数命名空间,作用域,嵌套,闭包
    centos7 搭建dns服务器
    centos7 搭建dhcp服务器
    Nginx 启用 gzip 压缩
    Eclipse 个人手册
    Nginx 命令
    定时任务
    系统设计
    根据 xsd 生成 jaxb java 类
  • 原文地址:https://www.cnblogs.com/quark/p/2158076.html
Copyright © 2011-2022 走看看