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循环。
我的疑问
- 什么操作属于修改集合
- 为什么不能修改集合 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; }
}
}
}