为什么c#dictionary foreach循环USUALLY以与插入相同的顺序迭代

时间:2017-01-09 11:18:21

标签: c# dictionary foreach hashtable

根据常见事实和许多赞成的答案,C#Dictionary在内部实施为Hashtable,如果我有正确的理解,则意味着:

  1. foreach语句在字典项目上迭代的顺序是不可预测的 - 因为在幕后,每个键都被哈希,并且根据此哈希值分配基础数组中的索引。

  2. 如果想要添加让我们说等于1, 2, 3, 4, 5, 6, 7的密钥,那么我们将以何种顺序执行操作并不重要,因为关于内部表示的唯一重要事项是密钥的哈希值。

  3. 如果我以不同的顺序添加相同的键,那么在该添加之后执行的foreach循环以与添加它们相同的顺序迭代键时,怎么可能呢?

    这种行为背后有什么神奇的实现?

    我用于测试的代码段:

    static int[] x =  { 3, 9, 5, 11, 12, 13, 2, 24, 137, 4, 1256, 67, 1, 125555 };
    
    static void Main(string[] args)
    {
        //to avoid any rehashing and collisions issues the capacity is manually set to a value >> than the number of stored items
        Dictionary<int, string> dict = new Dictionary<int, string>(10000);
    
        for (int i = 0; i < x.Length; i++)
        {
            dict.Add(x[i], x[i].ToString());
        }
    
        Console.WriteLine("Foreaching element of dict after inserting from left to right\n");
    
        foreach (var kvp in dict)//this displays items in exactly same order as they were added
        {
            Console.WriteLine(kvp.Key);
        }
    
        dict.Clear();
    
        for (int i = x.Length - 1; i >= 0; i--)
        {
            dict.Add(x[i], x[i].ToString());
        }
    
        Console.WriteLine("Foreaching element of dict after inserting from right to left\n");
    
        foreach (var kvp in dict)//this displays items in exactly same order as they were added
        {
            Console.WriteLine(kvp.Key);
        }
    }
    

1 个答案:

答案 0 :(得分:0)

问题部分与:How the Dictionary is internally maintained?相关。

简而言之,内部很少有单独的集合keysvaluesentriesbuckets,您可以在源代码中查找@Owen Pauling已发布的内容一个link

您应该对private void Insert(TKey key, TValue value, bool add)所需的Add方法感兴趣。

在您的问题的上下文中,发生的有趣部分是索引的计算,在您的情况下归结为index = count;。根据索引,将值添加到entries集合中,并为正确的存储桶设置值。

另一方面,在for循环期间,使用Enumerator实现初始化index=0;并且(在简单情况下)仅在MoveNext次调用期间递增它。这意味着迭代是根据entries集合完成的。因此,您可以在迭代期间观察与插入时相同的顺序。

如果多个条目属于同一个存储桶,则流程可能会更复杂一些,但在您的示例中并非如此。

<强> 注:

请注意,上述说明仅适用于您提交的案例。如果您尝试对字典进行某些修改,则会因为索引计算的流程略有不同而修改排序。例如,如果在显示键之前放置:

dict.Remove(12);
dict.Add(15, "15");

新添加的密钥将取代之前删除的密钥,因为在Insert方法中您会陷入if (freeCount > 0)条件。

另外请记住不要依赖内部实现,因为除非明确记录,否则它可能会改变。