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_TAAddressDefinitions
为IDictionary
,其中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语句是为了在键已经存在的情况下不引起异常。
在这种情况下,重构版本中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));