C#循环遍历同时执行删除操作

使用foreach

        List<int> arr = new List<int>() {1, 2, 4, 5};

        foreach (var num in arr)
        {
            if (num == 2)
            {
                arr.Remove(num);
            }
        }

这种做法会抛出异常:

InvalidOperationException: Collection was modified; enumeration operation may not execute.

原因是在使用foreach进行遍历时,本质上调用的是IEnumerator的MoveNext方法,看到它的实现如下:

            public bool MoveNext() 
            {
 
                List<T> localList = list;
 
                if (version == localList._version && ((uint)index < (uint)localList._size)) 
                {                                                     
                    current = localList._items[index];                    
                    index++;
                    return true;
                }
                return MoveNextRare();
            }
 
            private bool MoveNextRare()
            {                
                if (version != list._version) {
                    ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
                }
 
                index = list._size + 1;
                current = default(T);
                return false;                
            }
 

而在调用移除方法时,本质上调用的是RemoveAt方法,我们可以看到它的实现如下:

       public bool Remove(T item) 
       {
            int index = IndexOf(item);
            if (index >= 0) {
                RemoveAt(index);
                return true;
            }
 
            return false;
        }

        public void RemoveAt(int index) 
        {
            if ((uint)index >= (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }
            Contract.EndContractBlock();
            _size--;
            if (index < _size) {
                Array.Copy(_items, index + 1, _items, index, _size - index);
            }
            _items[_size] = default(T);
            _version++;
        }

如果必须要使用迭代器语法完成对元素的遍历和修改操作可以使用以下方法:

        List<int> arr = new List<int>() {1, 2, 4, 5};
        List<int> arrTmp = new List<int>(arr);

        foreach (var num in arrTmp)
        {
            if (num == 2)
            {
                arr.Remove(num);
            }
        }

 拷贝出一份原数组的副本,遍历副本的同时移除原数组种指定的元素。

使用for

第一种写法:

        int count = arr.Count;
        for (var index = 0; index < count; index++)
        {
            var num = arr[index];
            if (num == 2)
            {
                arr.Remove(num);
            }
        }

这种做法也会导致抛出异常:

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

原因是在RemoveAt方法中,当移除完元素之后,数组大小发生了变更,并且会将新的元素列表重新进行位置上的对齐:

        public void RemoveAt(int index) 
        {
            if ((uint)index >= (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }
            Contract.EndContractBlock();
            _size--;
            if (index < _size) {
                Array.Copy(_items, index + 1, _items, index, _size - index);
            }
            _items[_size] = default(T);
            _version++;
        }

第二种写法:

        for (var index = 0; index < arr.Count; index++)
        {
            var num = arr[index];
            if (num == 2)
            {
                arr.Remove(num);
            }
        }

第一眼看好像没有什么问题,但是实际上这种操作仍然存在问题,例如以下数组:

 List<int> arr = new List<int>() {1, 2, 2, 4, 5};

在执行完上述代码后2没有删除干净。

这是因为当i=1时,满足条件执行删除操作,会移除第二个元素2,接着第三个元素会前移到第二个元素的位置,即i=1对应的是第二个元素。

此时数组中的元素如下:

1,2,4,5

接着遍历i=2,也就跳过第二个2。这就造成部分元素删除。

正确的做法是逆序遍历,才能保证删除的正确性。

        for (var index = arr.Count - 1; index >= 0; index--)
        {
            var num = arr[index];
            if (num == 2)
            {
                arr.Remove(num);
            }
        }

Guess you like

Origin blog.csdn.net/dmk17771552304/article/details/120685227