C# 使用Linq进行复制/过滤会在foreach循环中产生动态结果吗?
所以我创建了一个我想删除的项目字典的投影C# 使用Linq进行复制/过滤会在foreach循环中产生动态结果吗?,c#,linq,foreach,C#,Linq,Foreach,所以我创建了一个我想删除的项目字典的投影 var toRemoveList = this.outputDic.Keys.Where(key => this.removeDic.ContainsKey(key)); 然后我遍历从实际字典中删除的结果 foreach(var key in toRemoveList) this.outputDic.Remove(key); 但是,在foreach期间,会抛出一个异常,说明列表在循
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key));
然后我遍历从实际字典中删除的结果
foreach(var key in toRemoveList)
this.outputDic.Remove(key);
但是,在foreach期间,会抛出一个异常,说明列表在循环期间被修改。但是,怎么会这样呢?linq查询是否有点动态,并且每次字典更改时都会重新计算?在查询结束时调用一个简单的.ToArray()可以解决问题,但在我看来,它根本不应该出现。而.ToArray()
可以解决问题,因为它强制您计算整个枚举并缓存本地值。如果不这样做,当您通过它枚举时,可枚举对象将尝试计算第一个索引,返回该索引,然后返回集合并计算下一个索引。如果要迭代的基础集合发生更改,则不能再保证枚举将返回适当的值
简而言之:只需使用
.ToArray()
(或.ToList()
,或其他任何工具)强制求值即可。出现此错误的原因是延迟执行linq。要在循环运行时充分解释它,实际上是从字典中获取数据时因此,此时会对outputdic进行修改,不允许修改正在循环的集合。这就是为什么会出现这个错误。您可以通过在运行循环之前要求编译器执行来消除此错误
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key)).ToList();
注意上面语句中的ToList()。它将确保您的查询已被执行,并且您的列表位于toRemoveList中。LINQ查询使用延迟执行。它一个接一个地流式处理项目,根据请求重新运行它们。因此,是的,每次尝试删除一个键时,它都会更改结果,这就是它抛出异常的原因。当您调用
ToArray()
时,它会强制执行查询,这就是它工作的原因
编辑:这在某种程度上是对您的评论的回应。检查这是执行for each时使用的机制。您的查询只是转换成一个表达式树,在检索元素时,过滤器、项目、操作等会逐个应用于元素,除非这是不可能的。原因是
toRemoveList
不包含要删除的内容列表,它包含如何获取可删除内容列表的说明
如果您使用F11在调试器中逐步执行此操作,您可以清楚地看到这一点。它停止的第一个点是,光标位于foreach
上,这是您所期望的
接下来,您将停在toRemoveList
(foreach中的foreach(toRemoveList中的var键)
)。这就是它设置迭代器的地方
但是,当您单步通过var key
(使用F11)时,它现在跳转到toRemoveList
的原始定义,特别是this.removeDic.ContainsKey(key)
部分。现在你了解了真正发生的事情
foreach
正在调用迭代器Next
方法,以移动到字典键中的下一个点并保留列表。当您调用此.outputDic.Remove(键)时代码>这检测到迭代器尚未完成,因此会使用此错误阻止您
正如大家在这里所说的,解决这个问题的正确方法是使用ToArray()
/ToList()
,因为它们所做的是给您列表的另一个副本。因此,您有一个要跨出,还有一个要从中删除
所以我创建了一个我想删除的项目字典的投影
var toRemoveList =
this.outputDic.Keys.Where(key =>
this.removeDic.ContainsKey(key));
正如我经常说的,如果我能教人们一件关于LINQ的事情,那就是:查询表达式的结果是查询,而不是执行查询的结果。现在有了一个对象,它的意思是“字典的键,因此键是……某物”。不是那个查询的结果,而是那个查询。查询本身就是一个对象;它不会给你一个结果集,直到你要求一个
然后你要这样做:
foreach(var key in toRemoveList)
this.outputDic.Remove(key);
那你在干什么?您正在对查询进行迭代。在查询上迭代执行查询,因此查询在原始字典上迭代。但是,当您在字典上进行迭代时,您会从字典中删除一个条目,这是非法的
依我看,这根本就不应该发生
你对世界应该是什么样的看法是很普遍的,但你这样做会导致严重的效率低下。假设创建查询会立即执行查询,而不是创建查询对象。这有什么用
var query = expensiveRemoteDatabase
.Where(somefilter)
.Where(someotherfilter)
.OrderBy(something);
对Where
的第一次调用生成一个查询,然后在您的世界中执行该查询,从远程数据库中提取与该查询匹配的所有记录。对的第二个调用,其中
会说“哦,对不起,我的意思是在这里也应用这个过滤器,我们可以再次执行整个查询吗,这次使用第二个过滤器?”然后计算整个记录集,然后我们说“哦,不,等等,我忘了告诉你们当你们构建最后一个查询对象时,我们需要对它进行排序,所以数据库,你们能帮我第三次运行这个查询吗?”
现在,您可能明白了为什么查询会生成一个查询,然后直到需要时才执行它了吗?foreach(toRemoveList.toList()中的var键)
如果您在循环中向列表添加内容会怎么样?这可能就是一个无限循环。^他说的。区别在于Linq
查询是惰性执行的(获取下一个,停止工作,检查是否有另一个?获取下一个,停止工作…检查是否有另一个?)在“停止工作”步骤中,它遵从for循环的内容(在本例中)。我明白了,因此在本例中