不可变字典枚举顺序



有人问了一个类似于下面的问题,具体参考了这里的Dictionary:Dictionary的枚举器<TKey、TValue>按添加顺序返回键值对?和这里:字典枚举顺序

阅读这些内容,显然不应依赖字典的枚举顺序。根据Dictionary枚举的非确定性顺序,我最近观察到,当测试项目以目标为目标构建时,单元测试间歇性地失败(在构建机器上(。NET Core 3.1(在分支中(。相比之下,相同的测试项目构建了目标。NET Framework 4.7.2(在另一个分支上(没有失败。这些观察是针对许多单独的单元测试执行的。最终,我将失败归因于数值运算(1/x上的求和(,其中值(x(存储在用String键控的ImmutableDictionary中。在单元测试的情况下,求和的顺序会影响结果。已对计算进行了修复:使用了ImmutableSortedDictionary。

这里有一个演示ImmutableDictionary中键的不同顺序的精简代码片段(编译目标.NET Core 3.1并多次执行以观察不同的枚举(:

static void Main(string[] args)
{
var dict = ImmutableDictionary<string,double>.Empty;
for (int i = 0; i < 10; i++)
{
dict = dict.Add(i.ToString(),i);
}

Console.WriteLine("Keys collection: " + string.Join(", ",dict.Keys.ToList()));
Console.WriteLine("Keys during enumeration: " +string.Join(", ", dict.Select(c => c.Key).ToList()));
}

然而,如在对关于Dictionary的问题的回答中所指出的:;CCD_ 4确实以相同的顺序返回项目(假设您没有触发哈希表的调整大小(";。同样,我知道不应该依赖当前的排序行为,但不清楚在什么情况下(例如,当使用.NET Framework、.NET Standard、.NET Core时(执行之间的排序实际上不同。我的问题是:

为什么ImmutableDictionary(在.NET Framework 4.7.2中(在执行之间以相同的顺序返回项,而Immutable字典(在.NET Core 3.1中(始终以不同的顺序返回项目?

因为"字符串";在里面NET核心是不确定的。

这里的问题取决于您使用的密钥类型。如果您使用string作为密钥类型(我在这里做了一个有根据的猜测,这就是您所使用的(,在中。NET Core中,您会遇到同一字符串的哈希代码在每次执行应用程序时都不同的问题。

你可以在这里阅读更多关于

在。NET Framework中,相同的字符串在每次执行时生成相同的哈希代码,因此在枚举过程中它们的顺序始终保持不变。

对于您的情况,您可以尝试切换到一种类型,在该类型中,您可以通过类型本身(例如int(拥有确定性哈希函数,或者为类型提供自定义哈希函数。

尽管在最初的问题中还有一个后续问题——为什么是Dictionary<字符串,x>确定性地枚举,但是ImmutableDictionary<字符串,x>枚举是非确定性的,如果两者都是在字符串上键入的,并且字符串在每次执行应用程序时生成不同的散列。

这里的答案是枚举器如何为每种类型工作。对于字典<TKey、TValue>在Core中,基本上有两个集合,hashes和entries(请参阅本文中的图表(。Dictionary的枚举使用条目,并且总的来说,条目按照添加的顺序出现,所以它与哈希函数无关。您可以在此处的KeyCollection的自定义枚举器中看到枚举器代码。

然而,对于ImmutableDictionary,枚举跟随散列(请参阅ImmutableDictionary中调用的HashBucket.Enumerator(。因此,在Framework中,字符串的哈希一致,一切都很好,哈希保持了它们的顺序。不过,现在在Core中,使用字符串键,每次运行的哈希都不同,它们的求值位置不同,因此它们的顺序也不同。

希望能覆盖它。

列表的顺序是确定的,但字典不是。所以解决这个问题,你可以将两者混合:

public class DeterministicThing<TKey, TValue> :
IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull
{
private readonly ImmutableList<KeyValuePair<TKey, TValue>> kvpList;
private readonly ImmutableDictionary<TKey, TValue> kvpDictionary;
public DeterministicThing(IEnumerable<KeyValuePair<TKey, TValue>> kvps)
{
kvpList = kvps.ToImmutableList();
kvpDictionary = kvps.ToImmutableDictionary(pair => pair.Key, pair => pair.Value);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => kvpList.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => kvpList.GetEnumerator();
public TValue this[TKey key] => kvpDictionary[key];
public int Count => kvpList.Count;
}

最新更新