Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用Linq进行复制/过滤会在foreach循环中产生动态结果吗?_C#_Linq_Foreach - Fatal编程技术网

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循环的内容(在本例中)。我明白了,因此在本例中