LINQ where语句的条件会在循环期间重新求值吗?

本文关键字:新求值 循环 语句 where 条件 LINQ | 更新日期: 2023-09-27 18:11:10

我有这个foreach -loop:

foreach (var classId in m_ClassMappings[taAddressDefinition.Key])
{
    if(!m_TAAddressDefinitions.ContainsKey(classId))
    {
        m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value);
    }
}

m_TAAddressDefinitionsIDictionary,其中classId作为唯一的Key值。
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语句是为了在键已经存在的情况下不引起异常。

LINQ where语句的条件会在循环期间重新求值吗?

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

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

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

这个概念称为闭包。有关更多深入信息,请参阅Jon Skeet的回答:什么是'闭包'在。net ?。他的文章《闭包之美》是一篇很好的代码阅读。

您没有修改m_ClassMappings而是m_TAAddressDefinitions,因此CollectionModifiedException不会被引发。

最好重写为

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