亲宝软件园·资讯

展开

.Net5 下Dictionary 为什么可以在foreach中Remove

通信的搞程序 人气:0

  在一个讨论群里,看见有人说Dictionary可以在foreach中直接调用Remove了,带着疑问,写了简单代码进行尝试

  

class Program
    {
        static void Main(string[] args)
        {

            var dic = Enumerable.Range(1, 10).ToDictionary(t => t, t => t);
            foreach (var i in dic)
            {
                if (i.Key.GetHashCode() % 2 == 0)
                {
                    dic.Remove(i.Key);
                }
                else
                {
                    Console.WriteLine($"{i.Key}");
                }
            }
            Console.WriteLine("Hello World!");
        }
    }

  执行果然没有报错,输出正常。

  

 

  终于不再需要进行单独执行Remove

 

  要想知道为啥在.Net Framework上不行,在.Net5下却可以,就需要知道在.Net5中Dictionary有着什么样的变化

  .Net Framework中源码         .Net5中源码

       我们看下两者有什么区别:

  Framework中是这样的:

  

 1 public bool MoveNext() {
 2                 if (version != dictionary.version) {
 3                     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
 4                 }
 5  
 6                 // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
 7                 // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
 8                 while ((uint)index < (uint)dictionary.count) {
 9                     if (dictionary.entries[index].hashCode >= 0) {
10                         current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value);
11                         index++;
12                         return true;
13                     }
14                     index++;
15                 }
16  
17                 index = dictionary.count + 1;
18                 current = new KeyValuePair<TKey, TValue>();
19                 return false;
20             }
View Code

  

  .Net5中是这样的:

  

 1 public bool MoveNext()
 2             {
 3                 if (_version != _dictionary._version)
 4                 {
 5                     ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
 6                 }
 7 
 8                 // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
 9                 // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
10                 while ((uint)_index < (uint)_dictionary._count)
11                 {
12                     ref Entry entry = ref _dictionary._entries![_index++];
13 
14                     if (entry.next >= -1)
15                     {
16                         _current = new KeyValuePair<TKey, TValue>(entry.key, entry.value);
17                         return true;
18                     }
19                 }
20 
21                 _index = _dictionary._count + 1;
22                 _current = default;
23                 retur
View Code

 

  细看好像两者并没什么很明显的区别。我们知道,在对Dictionary进行操作的时候,_version会自增改变,从而导致报错。难道.Net5中进行Remove操作_version不会改变。

  .Net5中Remove代码:

  

 1 public bool Remove(TKey key)
 2         {
 3             // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
 4             // statement to copy the value for entry being removed into the output parameter.
 5             // Code has been intentionally duplicated for performance reasons.
 6 
 7             if (key == null)
 8             {
 9                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
10             }
11 
12             if (_buckets != null)
13             {
14                 Debug.Assert(_entries != null, "entries should be non-null");
15                 uint collisionCount = 0;
16                 uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode());
17                 ref int bucket = ref GetBucket(hashCode);
18                 Entry[]? entries = _entries;
19                 int last = -1;
20                 int i = bucket - 1; // Value in buckets is 1-based
21                 while (i >= 0)
22                 {
23                     ref Entry entry = ref entries[i];
24 
25                     if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer<TKey>.Default.Equals(entry.key, key)))
26                     {
27                         if (last < 0)
28                         {
29                             bucket = entry.next + 1; // Value in buckets is 1-based
30                         }
31                         else
32                         {
33                             entries[last].next = entry.next;
34                         }
35 
36                         Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
37                         entry.next = StartOfFreeList - _freeList;
38 
39                         if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>())
40                         {
41                             entry.key = default!;
42                         }
43 
44                         if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>())
45                         {
46                             entry.value = default!;
47                         }
48 
49                         _freeList = i;
50                         _freeCount++;
51                         return true;
52                     }
53 
54                     last = i;
55                     i = entry.next;
56 
57                     collisionCount++;
58                     if (collisionCount > (uint)entries.Length)
59                     {
60                         // The chain of entries forms a loop; which means a concurrent update has happened.
61                         // Break out of the loop and throw, rather than looping forever.
62                         ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
63                     }
64                 }
65             }
66             return false;
67         }

  一看果然_version不会变化。看到这可能会有喊知乎内行啊,一行代码就解决问题,那为什么Framework中不这样做呢。相关提交讨论

  

 
 

加载全部内容

相关教程
猜你喜欢
用户评论