zoukankan      html  css  js  c++  java
  • .Net5 下Dictionary 为什么可以在foreach中Remove

      在一个讨论群里,看见有人说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中不这样做呢。相关提交讨论

      

     
  • 相关阅读:
    堆排序
    jdk8 永久代变更
    oracle 区分大小写遇到的坑
    日志统计分析
    zookeeper 服务挂掉重启后,dubbo 服务是不会自动重新注册上的
    代码质量管理
    快速排序算法
    python flask 项目结构
    项目架构
    JS中的循环---最全的循环总结
  • 原文地址:https://www.cnblogs.com/gsjlovenet/p/14543924.html
Copyright © 2011-2022 走看看