C# 为什么迭代字典';s值的返回顺序与插入顺序相同,但它应该是不确定的?

C# 为什么迭代字典';s值的返回顺序与插入顺序相同,但它应该是不确定的?,c#,dictionary,C#,Dictionary,众所周知,C#中的字典理论上并不保留插入元素的顺序 出于枚举目的,字典中的每个项都被视为表示值及其键的KeyValuePair结构。返回项目的顺序未定义 然而,当我将随机键插入字典并在foreach循环中读取它们时,它们总是按照插入顺序返回 Random rand = new Random(); var map = new Dictionary<int, int>(); for (int i = 0; i < 10; ++i) { var key = rand.Nex

众所周知,C#中的字典理论上并不保留插入元素的顺序

出于枚举目的,字典中的每个项都被视为表示值及其键的
KeyValuePair
结构。返回项目的顺序未定义

然而,当我将随机键插入字典并在foreach循环中读取它们时,它们总是按照插入顺序返回

Random rand = new Random();
var map = new Dictionary<int, int>();
for (int i = 0; i < 10; ++i)
{
    var key = rand.Next(1000000000);
    var value = 0;

    Console.WriteLine($"Key: {key}");

    if (map.ContainsKey(key))
        continue;

    map.Add(key,value);
}

Console.WriteLine("Reading Back..");
foreach (var pair in map)
{
    Console.WriteLine($"Key: {pair.Key}");
}
Random rand=new Random();
var map=newdictionary();
对于(int i=0;i<10;++i)
{
var key=rand.Next(100000000);
var值=0;
WriteLine($“Key:{Key}”);
if(地图容器(图例))
继续;
添加(键、值);
}
Console.WriteLine(“读回…”);
foreach(映射中的变量对)
{
Console.WriteLine($“Key:{pair.Key}”);
}
此代码的输出如下所示:

Key: 593548784 Key: 765023454 Key: 1460074 Key: 913286745 Key: 804757753 Key: 281489700 Key: 395818098 Key: 227086287 Key: 323530161 Key: 629618868 电话号码:593548784 钥匙:765023454 关键字:1460074 电话号码:913286745 电话号码:804757753 关键字:281489700 电话号码:395818098 电话号码:227086287 电话号码:323530161 钥匙:629618868 回读

Key: 593548784 Key: 765023454 Key: 1460074 Key: 913286745 Key: 804757753 Key: 281489700 Key: 395818098 Key: 227086287 Key: 323530161 Key: 629618868 电话号码:593548784 钥匙:765023454 关键字:1460074 电话号码:913286745 电话号码:804757753 关键字:281489700 电话号码:395818098 电话号码:227086287 电话号码:323530161 钥匙:629618868 我很惊讶,因为我认为在将元素插入字典时,哈希键是通过取key字段的mod N来确定的,这意味着顺序将丢失(除非N非常大)


有人知道为什么会这样吗

字典不支持排序的事实并不意味着每次读取数据时都会以随机顺序获取数据。您通常会按照插入的顺序对其进行迭代,但是:

出于枚举的目的,字典中的每个项都被视为表示值及其键的KeyValuePair结构返回项目的顺序未定义。

当您开始删除、插入和添加其他条目时,原始顺序可能会丢失。您可以在的答案中找到更多详细信息

下面是删除项目的示例代码

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace SO59038883
{
    internal class Program
    {
        static void Main()
        {
            // keep track of the key that have been inserted
            // so we can remove a random
            var trackedKeys = new List<int>();

            var rand = new Random();
            var map = new Dictionary<int, int>();
            for (var i = 0; i < 20; i++)
            {
                var key = rand.Next(1000000000);
                trackedKeys.Add(key);
                Console.WriteLine($"Key: {key}");

                if (map.ContainsKey(key))
                    continue;

                map.Add(key, i);

                // Let's remove three random keys
                // at set interval
                if (i == 5 || i == 10 || i == 15)
                {
                    // get a random key from our tracked key list
                    var index = rand.Next(trackedKeys.Count);
                    var keyToRemove = trackedKeys[index];
                    map.Remove(keyToRemove);
                }
            }

            Console.WriteLine("Reading Back..");
            foreach (var pair in map)
            {
                Console.WriteLine($"Key: {pair.Key} - Value: {pair.Value}");
            }

            if (Debugger.IsAttached)
            {
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey();
            }
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
名称空间SO5903883
{
内部课程计划
{
静态void Main()
{
//跟踪已插入的密钥
//所以我们可以移除一个随机变量
var trackedKeys=新列表();
var rand=new Random();
var map=newdictionary();
对于(变量i=0;i<20;i++)
{
var key=rand.Next(100000000);
trackedKeys.Add(key);
WriteLine($“Key:{Key}”);
if(地图容器(图例))
继续;
地图。添加(键,i);
//让我们删除三个随机键
//每隔一段时间
如果(i==5 | | i==10 | | i==15)
{
//从跟踪的密钥列表中获取随机密钥
var index=rand.Next(trackedKeys.Count);
var keyToRemove=跟踪键[索引];
map.Remove(按键移动);
}
}
Console.WriteLine(“读回…”);
foreach(映射中的变量对)
{
WriteLine($“Key:{pair.Key}-Value:{pair.Value}”);
}
if(Debugger.IsAttached)
{
Console.WriteLine(“按任意键继续…”);
Console.ReadKey();
}
}
}
}

字典的实现方式不是将项目直接存储到哈希表中,而是使用一个整数哈希表,然后用它来标识不断增长的数组中的项目。如果从未从
字典中删除任何内容
,则此数组将按添加顺序包含项;这种行为从.NET诞生之初就一直存在。如果项目被删除,未来项目在数组中的放置顺序可能就不那么可预测了,我并不特别希望它在所有.NET版本中都是一致的。

而且,没有任何行为的保证。Experimantation为您提供了一个快照,该快照可能会在下一版本中更改。@HenkHolterman:所描述的行为是MS应该为“从未删除过任何项目”场景IMHO记录的行为。在通过从文件导入数据来构建字典的场景中,让字典的转储匹配文件中项目的顺序比让它们以混乱的顺序输出数据要好,拥有
Dictionary
为“从未删除的项目”场景保持项目有序比用户代码在支持随机查找的同时保持项目有序的任何方法都要便宜。有一个不断增长的
条目
数组,还有一个
自由列表
索引。稍微混合一下Remove()和Add()。我注意到这类文档有一个通用模式。如果他们指出,项目的顺序可能并不总是一致的,这几乎总是意味着项目的顺序通常是一致的,只是不要指望它,因为有一些案例表明情况并非如此。如果您所做的只是添加到字典中,那么顺序将被保留。开始混合添加和删除,会有点奇怪,非决定论和未定义的行为是完全不同的概念。如果它真的是不确定的,那么每次的顺序都可能不同。未定义的行为意味着,“我们可以在将来自由地改变它的工作方式,所以不要依赖于您看到的内容。”虽然链接(作为副本)是针对C/C++的,但对“未定义的内容”的解释完全相同-@Clay指出“未定义的内容”