I am trying to implement a caching mechanism for enumerating collections safely, and I am checking if all modifications of the built-in collections are triggering an I
To clarify when the change was made available and via which methods...
Microsoft's docs on the Dictionary's .Remove and .Clear have been updated:
.NET Core 3.0+ only: this mutating method may be safely called without invalidating active enumerators on the Dictionary<TKey,TValue> instance. This does not imply thread safety.
.NET Core 3.0 came with C# 8.0. Since then, we have been able to modify a Dictionary<TKey,TValue> during enumeration (foreach
) via .Remove and .Clear only.
This appears to be an intentional difference between .Net full framework and .Net core for Dictionary<TKey, TValue>
.
The divergence occurred in Pull #18854: Remove version increment from Dictionary.Remove overloads:
Removes the version increment from Remove operations
This addresses the coreclr side of the api change Add Dictionary.Remove(predicate) with the intention of allowing removal of items from the dictionary while enumerating per direction from @jkotas . All collections tests and modified and new tests added in the related corefx PR.
There appears to be an open documentation issue:
Issue #42123: Clarify Dictionary behavior/guarantees around mutation during enumeration:
Is it correct to say that the current implementation of Dictionary supports non-concurrent mutation during iteration?
Only removal. This was enabled as a feature in dotnet/coreclr#18854.
is this something that can be depended on going forward
Yes.
We should ensure the docs are updated to reflect this.
You might want to add a vote to the open doc issue requesting clarification as the .Net core 3.0 documentation for Dictionary<TKey,TValue>.GetEnumerator() is now obsolete:
If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to
MoveNext
orIEnumerator.Reset
throws anInvalidOperationException
.
Strangely enough, the enumerator for SortedDictionary<TKey, TValue> does throw when the dictionary is modified during enumeration.
Demos:
Remove()
: https://dotnetfiddle.net/8vONOw (throws).Remove()
: https://dotnetfiddle.net/es6STm (does not throw).Add()
: https://dotnetfiddle.net/6q7Lvx (throws).Remove()
from SortedDictionary<int, string>
: https://dotnetfiddle.net/bssrG7 (throws).