The reason is Dictionary and ConcurrentDictionary have different Purposes.
ConcurrentDictionary - supposed to deals with concurrency issues(editing from different threads) while Dictionary will give you a better performance.
The reason to the different behavior is: different implementation of GetEnumerator() method.
Now I will explain the reason for the exception with Dictionary and the reason you do not get exception with ConcurrentDictionary.
The foreach statement is syntactic sugar for something like:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
The "GetEnumerator()" in Dictionary returns new instance of struct named: "Enumerator"
This structure implements: IEnumerator >KeyValuePair>TKey,TValue>>, IDictionaryEnumerator and his C'tor looks like:
internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
this.getEnumeratorRetType = getEnumeratorRetType;
current = new KeyValuePair<TKey, TValue>();
}
The implementation of MoveNext() inside "Enumerator" verify first that the source Dictionary wasn't modified:
bool moveNext(){
if (version != dictionary.version) {
throw new InvalidOperationException....
}
//the itarate over...
}
The "GetEnumerator()" in ConcurrentDictionary implemented a way different:
IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(){
Node[] buckets = m_tables.m_buckets;
for (int i = 0; i < buckets.Length; i++)
{
Node current = Volatile.Read<Node>(ref buckets[i]);
while (current != null)
{
yield return new KeyValuePair<TKey, TValue>(current.m_key, current.m_value);
current = current.m_next;
}
}
}
In this implementation there is a technic called "lazy evaluation" the return statement will return the value.
When the consumer calls MoveNext() then you will return to "current = current.m_next;"
So, there is no "not change" verification inside GetEnumerator().
if you want to avoid exception with the "Dictionary editing" then:
1. iterate to the element you want to remove
2. remove the element
3. break before MoveNext() called
In your example:
foreach (var d in dict2)
{
if (dict2.ContainsKey(1))
dict2.Remove(1);
if (dict2.ContainsKey(3))
dict2.Remove(3);
break; // will prevent from exception
}
for more information about the GetEnumerator() of ConcurrentDictionary:
https://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx