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.
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));
精彩评论