Collection was modified; enumeration operation may not execute.

542 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

其实这是一个很低级的错误,大概率都会犯这种错误!一次想明白了,以后就可以杜绝这种低级错误。

错误重现

开门见山,想必看到文章标题就已经可以猜的差不多了:集合被修改后,无法再进行迭代循环操作

为使错误重现,写一段简单的代码

public static void Main(string[] args)
{
    var list = new List<FData>()
    {
        new FData{Code="N09092",Name="ADMIN"},
        new FData{Code="N23213",Name="XLSEE"},
        new FData{Code="N45444",Name="TESTS"},
        new FData{Code="N66697",Name="WWWQW"},
        new FData{Code="N11123",Name="FOOS"}
    };
    foreach(var item in list)
    {
        if (item.Name == "WWWQW") list.Remove(item);

        Console.WriteLine("Code: {0}, Name: {1}\n", item.Code, item.Name);
    }
    Console.ReadLine();
}

public class FData
{
    public string Code { get; set; }
    public string Name { get; set; }
}

代码执行后,报错如下 image.png

看到错误第一感觉是懵的,这也会报错?
想都没想,直接看代码是不是自己写错了,或者编译有问题
反复测试后,确定就是我代码有问题,而且是大问题!

既然删除不行,那就修改,这下不报错了 image.png

再尝试新增(其实这种需求也有,比如再某一条数据后追加一条数据) image.png

错误原因分析

经过上面三个方向的测试,基本得出,增删都会报错,而修改却没有问题....猜也能猜出来,离真相不远了!

分析:在删除了一项,集合的元素个数是变化的。新增也会使它长度发生变化。
而在长度发生变化后,此时元素会重排,第二个元素的索引由1变为0(假设删除的是第一个元素),后面的依次往前移动。而此时Count的值也-1
而在foreach中,索引和Count的值是不允许被修改的,否则内部对比机制就会抛出异常!
在修改元素时,无论怎么修改,索引和Count值不会发生改变的!

既然这样,那我想到一个sao操作

foreach (var item in list)
{
    var i = list.Count - list.IndexOf(item) - 1;
    var temp = item;
    item = list[i];
    list[i] = temp;
    Console.WriteLine("Code: {0}, Name: {1}\n", item.Code, item.Name);
}

想必写过冒泡算法的对这个不会陌生
我把首尾元素对调一下
猜猜看看可行不可行?

直接看结果 image.png

直接报错,编译器都编译不过去....

list[i] = temp;虽然没报错,但是跟他上一行差不多,估计运行时也会报错!

没事,咱还有sao操作,继续折腾

foreach (var item in list)
{
    var i = list.Count - list.IndexOf(item) - 1;
    var temp = item;
    item.Code = list[i].Code;
    item.Name = list[i].Name;
    list[i].Code = temp.Code;
    list[i].Name = temp.Name;
    Console.WriteLine("Code: {0}, Name: {1}\n", item.Code, item.Name);
}

image.png

这下完美了,通过了编译,并且成功运行!

说明了我前面的分析,并且item中有其他属性也是不能修改的(比如索引或者指针之类的...这里不深究了)

解决办法

既然有问题,除了分析问题,还要解决问题

直接弃用foreach做这种操作不就行了....这活你干不了,有的是人能干!

可以用for循环

for (int i = 0; i < list.Count; i++)
{
    if (list[i].Name == "WWWQW") list.Remove(list[i]);
    Console.WriteLine("{0}  Code: {1}, Name: {2}\n", i, list[i].Code, list[i].Name);
}

image.png

其实最保险的方式是:从后往前移除,这样前面的索引就不会变了,变的只是count <--- 听我的准没错!

将IEnumerable先转化为数组(Array),然后再执行删除/新增操作。

这里调用的就是Array的ForEach方法

Array.ForEach(list.ToArray(), x =>
{
    if (x.Name == "WWWQW") list.Remove(x);
    Console.WriteLine("Code: {0}, Name: {1}\n", x.Code, x.Name);
});

image.png

其实这种方法不太好写,而且感觉是为了用而用。
不过需求能实现

至于其他肯定还有方法,比如Linq还没出场呢
这里就不过多介绍了

文末总结

生活处处是学问,也许不经意的常见的一个小东西,蕴含着大道理。
解决问题不是终点,还需要sao操作一把,摸透,才能慢慢接近真象,记忆才会深刻,理解才会彻底!
其实很多时候就像断案一样,把握细节,方能找出蛛丝马迹
反正这玩意,多思考,多换几个方向,大胆猜测,又不要钱[dog]