C# LINQ Where语句上的条件会在循环期间重新计算吗?

C# LINQ Where语句上的条件会在循环期间重新计算吗?,c#,.net,linq,refactoring,C#,.net,Linq,Refactoring,我有一个foreach-循环: foreach (var classId in m_ClassMappings[taAddressDefinition.Key]) { if(!m_TAAddressDefinitions.ContainsKey(classId)) { m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value); } } m_TAAddressDefinitions是一个i

我有一个
foreach
-循环:

foreach (var classId in m_ClassMappings[taAddressDefinition.Key])
{
    if(!m_TAAddressDefinitions.ContainsKey(classId))
    {
        m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value);
    }
}
m_TAAddressDefinitions
是一个
i字典
,其中classId用作唯一键值。
Resharper建议将
if
-语句更改为LINQ筛选器,如下所示:

foreach (var classId in m_ClassMappings[taAddressDefinition.Key].Where(
    classId => !m_TAAddressDefinitions.ContainsKey(classId)))
{
    m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value);
}
我认为这可能无法按预期工作,因为
m_TAAddressDefinitions
集合的内容在循环内部发生变化,这导致LINQ筛选器条件的源(
classId=>!m_TAAddressDefinitions.ContainsKey(classId)
)在途中发生变化


如果在循环中添加两个具有相同值的
classId
,此操作是否会失败,或者在将值添加到集合时是否会重新计算LINQ条件?如果密钥已经存在,我的原始if语句不会导致异常。

您不是在修改
m_ClassMappings
,而是在修改
m_TAAddressDefinitions
,因此不会引发
CollectionModifiedException

最好重写成

m_ClassMappings[taAddressDefinition.Key]
  .Where(classId => !m_TAAddressDefinitions.ContainsKey(classId)))  
  .ToList()
  .ForEach(
    classId => m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value)); 

建议的更改不会改变逻辑。lambda表达式
classId=>!m_TAAddressDefinitions.ContainsKey(classId)
将被编译到一个匿名类中,您的字典将在其中传递。由于这是一个引用,而不是它的副本,对词典的更改将反映在评估中


这个概念被称为闭包。有关更深入的信息,请参阅Jon Skeet的回答:。他的帖子是一篇代码读物。

在这种情况下,重构版本中
Where
返回的
IEnumerable
将在迭代时惰性地生成值,从而达到您想要的效果。在这种情况下,包含
ToList
调用的建议不是您想要的,因为这会将
IEnumerable
具体化为一个集合,并且在
m_ClassMappings
集合中,您很容易出现重复的
classid


需要记住的是,
Where
调用中的谓词,即
classId=>!m_TAAddressDefinitions.ContainsKey(classId)
将针对每个项目进行评估,因为它是通过迭代
IEnumerable
生成的。所以在Resharper建议的版本中,如果
m_ClassMappings
中存在重复值,则遇到的第一个值将被添加到
m_TAAddressDefinitions
中,但当到达下一个重复值时,
Where
谓词将返回false,因为该值以前已添加到字典中。

我不担心修改在
m_ClassMappings
上出现异常,但如果先前已在同一循环中添加了classId,则在添加到
m_TAAddressDefinitions
时会出现“密钥已存在”异常。我对林克几乎没有什么经验。是过滤一次,在我的例子中是对结果进行循环,还是在每次循环迭代中重新执行过滤器?在LINQ中,
中的lamdba,其中
(以及任何方法)为每个循环求值-因此在调用
ToList
后,您将拥有字典中没有的键列表。您的解决方案是正确的。但一旦进入循环,列表将不会重新填充,因此,如果
m_ClassMappings[taAddressDefinition.Key]
中存在多个相等的classId,则调用Add将在第二次引发异常,因为从
m_TAAddressDefinitions
开始,没有一个classId,但是在循环过程中,条件会发生变化。用
ToList
具体化
IEnumerable
,并不是您想要的。请看我的答案。有时,更简洁的代码不利于更可读的代码。Resharper版本与旧版本完全相同,但正如您所发现的,它更难让您理解。在这种情况下,我建议跳过该重构。但我认为lambda只被调用一次(即使它是动态引用),因为Where语句只是用于过滤集合,并返回一个结果,该结果用作执行for循环的集合。不,因为LINQ查询使用。OK。读了之后,我知道你的答案也是正确的,所以我给你+1。很好的答案。这让我明白了它为什么有效。不管怎样,我仍然会使用我的原始版本,而不是LINQ-不是因为我不相信它能工作,而是它能做什么更清楚。