开发者

Will the condition on a LINQ Where-statement re-evaluate during the loop?

I have this foreach-loop:

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

m_TAAddressDefinitions is an IDictionary where classId is used as the unique Key value.

Resharper suggests to change the if-statement into into a LINQ filter like this:

foreach (var classId in m_ClassMappings[taAddressDefinition.Key].Where(
    classId => !m_TAAddressDefinitions.ContainsKey(classId)))
{
    m_TAAddressDefinitions.Add(classId, taAddressDefinition.Value);
}
开发者_JS百科

I am conserned if this might not work as expected, since the content of the m_TAAddressDefinitions collection is changing inside the loop, which causes the source of the LINQ filter-condition (classId => !m_TAAddressDefinitions.ContainsKey(classId)) to change on the way.

Will this fail if two classId with same value is added inside the loop, or will the LINQ condition be recalculated when values are added to the collection? My original if-statement was intended to not cause exception if the key already exist.


In this case, the fact that the IEnumerable returned by Where in the refactored version will lazily produce values as it is iterated over achieves what you want. The suggestion that includes the ToList call is not what you want in this case since that would materialize the IEnumerable as a collection and you would be vulnerable to there being duplicate classIds in the m_ClassMappings collection.

The thing to remember is that the predicate in the Where call, namely classId => !m_TAAddressDefinitions.ContainsKey(classId) will be evaluated for each item as it is produced as a result of iterating over the IEnumerable. So in the version suggested by Resharper, if there were duplicate values in the m_ClassMappings the first one encountered would be added to the m_TAAddressDefinitions but when the next duplicate is reached the Where predicate will return false because the value has been previously added to the Dictionary.


The suggested change does not alter the logic. The lambda expression classId => !m_TAAddressDefinitions.ContainsKey(classId) will be compiled into a anonymous class where your dictionary is passed in. Since this is a reference and not a copy of it changes to the dictionary will be reflected in the evaluation.

This concept is known as closure. See this answer from Jon Skeet for more in depth information: What are 'closures' in .NET?. And his post The Beauty of Closures is a very code read.


You're not modifying m_ClassMappings but m_TAAddressDefinitions, so the CollectionModifiedException will not be raised.

Better rewrite it to

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜