原因是Dictionary和ConcurrentDictionary有不同的用途。
ConcurrentDictionary - 应该处理并发问题(从不同线程编辑),而 Dictionary 将为您提供更好的性能。
不同行为的原因是:GetEnumerator() 方法的实现不同。
现在我将解释 Dictionary 出现异常的原因以及 ConcurrentDictionary 没有出现异常的原因。
foreach 语句是类似以下内容的语法糖:
var f = dict.GetEnumerator();
while (f.MoveNext())
{
var x = f.Current;
// your logic
}
字典中的“GetEnumerator()”返回名为“Enumerator”的结构的新实例
该结构实现: IEnumerator >KeyValuePair>TKey,TValue>>,IDictionaryEnumerator 和他的 C'tor 如下所示:
internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
this.getEnumeratorRetType = getEnumeratorRetType;
current = new KeyValuePair<TKey, TValue>();
}
“Enumerator”中 MoveNext() 的实现首先验证源字典是否未被修改:
bool moveNext(){
if (version != dictionary.version) {
throw new InvalidOperationException....
}
//the itarate over...
}
ConcurrentDictionary 中的“GetEnumerator()”实现了一种不同的方式:
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;
}
}
}
在此实现中,有一种称为“惰性求值”的技术,return 语句将返回值。
当消费者调用 MoveNext() 时,您将返回“current = current.m_next;”
因此,GetEnumerator() 内部不存在“不改变”验证。
如果你想避免“字典编辑”出现异常,那么:
1. 迭代到要删除的元素
2. 删除元素
3. 在调用 MoveNext() 之前中断
在你的例子中:
foreach (var d in dict2)
{
if (dict2.ContainsKey(1))
dict2.Remove(1);
if (dict2.ContainsKey(3))
dict2.Remove(3);
break; // will prevent from exception
}
有关 ConcurrentDictionary 的 GetEnumerator() 的更多信息:https://msdn.microsoft.com/en-us/library/dd287131(v=vs.110).aspx