在新的子集合中枚举时,集合被修改了exception

我知道在你枚举的同时更改集合会导致collection was modified exception 。 但是如果我从较大的子集中得到一个子集合,并且我在枚举该子列表时从一个较大的子集删除了一些项目,我仍然会收到此错误。 在子集合上调用ToList解决了此问题。 但为什么会这样呢?

 var localCollection = someData.ToList(); // from DB Context var localGrouped = localCollection.GroupBy(x => x.Id).Select(g => new { Id = g.Key, List = g.Select(x => x.Value) }); or .ToList(); // Here how I solve exception var groups = new List<List>(); while (localGrouped.Any()) { var newSelected = new List(); var firstGroup = localGrouped.First(); newSelected.Add(firstGroup.Id); localGrouped.Remove(firstGroup); var similiarGroups = localGrouped.Where(x => x.List.Intersect(firstGroup.List).Any()).ToList(); if (similiarGroups.Any()) { foreach (var similiarGroup in similiarGroups) { //Changing something here in parent collection causes exception newSelected.Add(similiarGroup.Id); localGrouped.Remove(similiarGroup); } } groupsOfParcels.Add(newSelected); } 

Where不是子集合,它是一个filter。 区别在于LINQ如何工作的核心。

你可以想到大致保存对原始IEnumerable的引用,以及一些检查条件的代码,以及一些说明它已经走了多远的状态。 当你在Where的输出上执行getNext()时,这段代码会遍历原始的IEnumerable直到找到满足条件的元素,然后返回它(或者到达原始IEnumerable的末尾,这意味着它也是在Where )结束时。

这是懒惰的评估 – 它只会在任何时候查看所需的任意数量的术语。 因此,原始的IEnumerable必须存在并且在整个过程中不进行修改。 如果您调用ToList() ,评估将立即进行 – 所有元素将被提取并放入列表中,然后继续。

关于这一点的最好的东西是C#和Linq神Jon Skeet。 他的post包括对所有主要Linqfunction的重新实现,并详细讨论了实现问题,以便您可以确切地了解它们(可能)的工作方式。

介绍

第2部分 – 其中(!)

GroupByWhereSelect和大多数其他LINQ操作只是从底层集合中获取IEnumerable并迭代它。 底层IEnumerable在尝试获取下一个项目时抛出exception,因为该集合已被修改。 通过每个LINQ运算符抛出该exception,因为如果它无法从基础序列中获取下一个项目,则它无法完成其工作。

通过使用ToList您可以在修改集合之前强制枚举整个序列,而不是允许将基础列表的枚举推迟到修改该列表之后。