所以我创建了一个我想删除的项目字典的投影。
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key));
然后我遍历从实际字典中删除的结果
foreach(var key in toRemoveList)
this.outputDic.Remove(key);
然而,在该foreach期间,抛出一个异常,说该列表在循环期间被修改。但是,怎么这样呢? linq查询是否有点动态,每次字典更改时都会重新评估?查询结尾处的一个简单的.ToArray()调用解决了问题,但是,它甚至不应该首先发生。
答案 0 :(得分:14)
所以我创建了一个我想删除的项目字典的投影。
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key));
正如我经常说的,如果我可以向人们传授关于LINQ的一件事,那就是:查询表达式的结果是查询,而不是执行查询的结果。你现在有一个对象,意思是“字典的键,使得键是......某种东西”。它不是该查询的结果,它是查询。查询本身就是一个对象;在你要求之前,它不会给你一个结果集。
然后你这样做:
foreach(var key in toRemoveList)
this.outputDic.Remove(key);
那你在做什么?您正在迭代查询。迭代查询执行查询,因此查询正在迭代原始字典。但是你在字典中删除了一个项目,在你迭代它时,这是非法的。
imo,它甚至不应该首先发生。
你对世界应该是怎样的观点是一个普遍的观点,但按照你的方式去做会导致效率低下。我们假设创建查询立即执行查询而不是创建查询对象。这是做什么的?
var query = expensiveRemoteDatabase
.Where(somefilter)
.Where(someotherfilter)
.OrderBy(something);
第一次调用Where
会产生一个查询,然后在您的世界中执行,从远程数据库中下拉与该查询匹配的所有记录。第二次调用Where
然后说“哦,对不起,我本来也想在这里应用这个过滤器,我们可以再次执行整个查询,这次使用第二个过滤器吗?”然后计算整个记录集,然后我们说“哦,不,等等,我忘了告诉你,当你构建最后一个查询对象时,我们需要对它进行排序,所以数据库,你可以第三次为我运行这个查询吗?“
现在也许您看到为什么查询产生查询然后在需要之前不会执行?
答案 1 :(得分:3)
原因是toRemoveList
不包含要删除的事物列表,它包含如何获取可以删除的事物列表的说明。
如果您使用F11 在调试器中逐步执行此操作,您可以自己清楚地看到这一点。它停止的第一个点是光标在foreach
上,这是你所期望的。
接下来,您将停在toRemoveList
(foreach(var key in toRemoveList)
中的那个)。这是设置迭代器的地方。
当您单步执行var key
(使用F11)时,它现在会跳转到toRemoveList
的原始定义,特别是this.removeDic.ContainsKey(key)
部分。现在,您可以了解到底发生了什么。
foreach
调用迭代器Next
方法移动到字典键中的下一个点并保留在列表中。当你调用this.outputDic.Remove(key);
时,它会检测到迭代器没有完成,因此会因为这个错误而阻止你。
正如大家在这里所说的那样,解决这个问题的正确方法是使用ToArray()
/ ToList()
,因为这样做会给你另一份列表副本。所以你有一个要逐步完成,一个要从中删除。
答案 2 :(得分:2)
.ToArray()
解决了这些问题,因为它会强制您评估整个枚举并缓存本地值。如果不这样做,当您通过它枚举可枚举尝试计算第一个索引时,返回该索引,然后返回到集合并计算下一个索引。如果您正在迭代更改的基础集合,则无法再保证枚举将返回适当的值。
简而言之:只需使用.ToArray()
(或.ToList()
或其他)强制进行评估。
答案 3 :(得分:2)
您收到此错误的原因是由于linq的延迟执行。在循环运行时完全解释它实际上是从字典中获取数据的时候。 因此,outputdic中的修改会在此时进行,并且不允许修改您正在循环的集合。这就是您收到此错误的原因。您可以通过要求编译器在运行循环之前执行它来消除此错误。
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key)).ToList();
注意上面语句中的ToList()。它将确保您的查询已执行,并且您的列表位于toRemoveList。
答案 4 :(得分:2)
LINQ查询使用延迟执行。它逐个流式传输项目,并在需要时对其进行重新调整。所以是的,每次尝试删除密钥时都会更改结果,这就是它抛出异常的原因。当你调用ToArray()
时,它会强制执行查询,这就是它工作的原因。
编辑:这有点回应你的意见。查看iterator blocks on msdn这是每个执行时使用的机制。您的查询只会转换为表达式树,并且在检索它们时,过滤器,项目,操作等将逐个应用于元素,除非不可能。