使用下面列出的普通字典代码,我得到了异常(exception)


Dictionary<int, int> dict2 = new Dictionary<int, int>();
dict2.Add(1, 10);
dict2.Add(2, 20);
dict2.Add(3, 30);
dict2.Add(4, 40);

foreach (var d in dict2)
{
    if (dict2.ContainsKey(2))
        dict2.Remove(2);

    if (dict2.ContainsKey(3))
        dict2.Remove(3);
}

但是对于 ConcurrentDictionary,这可以正常工作。
ConcurrentDictionary<int, int> dict1 = new ConcurrentDictionary<int, int>();
dict1.AddOrUpdate(1, 10, (k,v)=> 10);
dict1.AddOrUpdate(2, 20, (k, v) => 20);
dict1.AddOrUpdate(3, 30, (k,v)=> 30);
dict1.AddOrUpdate(4, 40, (k,v)=> 40);

foreach (var d in dict1)
{
    int x;
    if (dict1.ContainsKey(2))
        dict1.TryRemove(2, out x);

    if (dict1.ContainsKey(3))
        dict1.TryRemove(3, out x);
}

为什么会有行为差异?

最佳答案

原因是 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;
             }
         }
    }

在这个实现中,有一种称为“懒惰评估”的技术,返回语句将返回值。
当消费者调用 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

关于c# - 在 Dictionary 和 ConcurrentDictionary 之间修改集合时的不同行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28480150/

10-10 12:29