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-不是因为我不相信它能工作,而是它能做什么更清楚。