C# 集合已修改;可能无法执行枚举操作

循环删除集合中的元素,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ForeachCollection
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var collection = new List<Product>();
            collection.Add(new Product() { IsSaling = true, ProductName = "xiaomi" });
            collection.Add(new Product() { IsSaling = true, ProductName = "大米" });
            collection.Add(new Product() { IsSaling = true, ProductName = "黑豆" });
            collection.ForEach(x =>
            {
                collection.Remove(x);
            });
        }

        class Product
        {
            public string ProductName { get; set; }
            public bool IsSaling { get; set; }
        }
    }
}

运行起来结果报错了:

在网上搜了一下,很多人给出的回答是:不能在foreach中修改集合,如果真的需要修改就是用for循环。

我的疑问

  1. 什么操作属于修改集合
  2. 为什么不能修改集合 C#内部做了什么限制

带着这两个疑问,我在Remove方法上面按下了"F12"键,进入了List的源代码中。
下面是Remove方法源码

[__DynamicallyInvokable]
public bool Remove(T item)
{
    int num = IndexOf(item);
    if (num >= 0)
    {
        RemoveAt(num);
        return true;
    }

    return false;
}

[__DynamicallyInvokable]
void IList.Remove(object item)
{
    if (IsCompatibleObject(item))
    {
        Remove((T)item);
    }
}

[__DynamicallyInvokable]
public int RemoveAll(Predicate<T> match)
{
    if (match == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }

    int i;
    for (i = 0; i < _size && !match(_items[i]); i++)
    {
    }

    if (i >= _size)
    {
        return 0;
    }

    int j = i + 1;
    while (j < _size)
    {
        for (; j < _size && match(_items[j]); j++)
        {
        }

        if (j < _size)
        {
            _items[i++] = _items[j++];
        }
    }

    Array.Clear(_items, i, _size - i);
    int result = _size - i;
    _size = i;
    _version++;
    return result;
}

让我们继续来看Foreach方法的源码:

[__DynamicallyInvokable]
public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }

    int version = _version;
    for (int i = 0; i < _size; i++)
    {
        if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
        {
            break;
        }

        action(_items[i]);
    }

    if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
}

如果在ForEach期间对集合进行Remove的话,在下一趟循环的时候就会因为_version检查不通过,抛出异常。至此我解决了第二个疑问,原来集合在ForEach等迭代操作时会对_version进行检查
此外我通过在List源码中搜索_version关键字,终于解决了第一个疑问:对集合进行Clear、调用索引器赋值、Add、Remove等都属于对集合进行了修改,他们都会使_version++。

如果真的想要在循环中修改集合,需要怎么做呢?

第一种解决方法:使用for循环

第二种解决方法:调用ToArray()方法,然后再进行foreach循环

本文示例代码

using System.Collections.Generic;

namespace ForeachCollection
{
    /// <summary>
    /// 对于 集合已修改;可能无法执行枚举操作 的稍微深一步的探索
    /// </summary>
    internal class Program
    {
        static void Main(string[] args)
        {
            /*var dic = new Dictionary<string, int>();
            dic.Add("小米", 1);
            dic.Add("小里", 3);
            dic.Add("小黑", 2);
            foreach (var item in dic.Keys)
            {
                var dd = dic[item] + 1;
                dic[item] = dd;
                //执行完上面这一行后 由于索引器的Set方法会使得迭代器的version++ 
                //MoveNext的时候会检查这个version 变动了的话会抛异常
            }*/

            var collection = new List<Product>();
            collection.Add(new Product() { IsSaling = true, ProductName = "xiaomi" });
            collection.Add(new Product() { IsSaling = true, ProductName = "大米" });
            collection.Add(new Product() { IsSaling = true, ProductName = "黑豆" });
            collection.ForEach(x =>
            {
                //索引器赋值也会引发version++
                collection[collection.IndexOf(x)] = new Product() { IsSaling = x.IsSaling, ProductName = x.ProductName };

                //调用Remove方法,再List源码中,会调用 RemoveAt(int index) 最终会使version++
                //collection.Remove(x);
                /*下一次 ForEach的时候会检查 version++
                通过查看集合的源码可以发现 不要插入 不要移除 不要清空 不要排序 不要调用索引器对元素进行赋值 这些都会导致version++
                只要进行了以上任一操作 下次迭代的时候都会挂掉*/
            });

            //解决方法一:使用for循环
            /*for (int i = collection.Count - 1; i >= 0; i--)
            {
                var item = collection[i];
                collection.Remove(item);
            }*/

            //解决方法二:ToArray
            foreach (var item in collection.ToArray())
            {
                collection.Remove(item);
            }

        }

        class Product
        {
            public string ProductName { get; set; }
            public bool IsSaling { get; set; }
        }
    }
}

参考链接:

https://blog.csdn.net/qq_41872328/article/details/119105084

https://www.cnblogs.com/huangxincheng/p/13128901.html

posted @ 2023-03-15 22:43  BigBosscyb  阅读(1995)  评论(0编辑  收藏  举报