LINQ Where语句中的条件是否会在循环期间重新评估?

时间:2011-10-14 08:51:47

标签: 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_TAAddressDefinitionsIDictionary,其中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语句意图不会导致异常。

3 个答案:

答案 0 :(得分:3)

在这种情况下,重构版本中由IEnumerable返回的Where将在迭代时懒散地生成值,从而实现您想要的效果。在这种情况下,包含ToList调用的建议你想要什么,因为这会使IEnumerable成为一个集合,你很容易受到重复{{ 1 {}在classIds集合中。

要记住的是,m_ClassMappings调用中的谓词,即Where,将根据classId => !m_TAAddressDefinitions.ContainsKey(classId)的迭代结果为每个项目进行评估。因此,在Resharper建议的版本中,如果IEnumerable中存在重复值,则遇到的第一个值将添加到m_ClassMappings但是当到达下一个副本时,m_TAAddressDefinitions谓词将返回false,因为该值以前已添加到Dictionary中。

答案 1 :(得分:2)

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

这个概念被称为 closure 。有关更深入的信息,请参阅Jon Skeet的答案:What are 'closures' in .NET?。他的帖子The Beauty of Closures是一个非常代码阅读。

答案 2 :(得分:0)

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

更好地将其重写为

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