【问题标题】:ImmutableDictionary enumeration orderImmutableDictionary 枚举顺序
【发布时间】:2020-07-16 05:55:42
【问题描述】:

已经提出了与以下类似的问题,具体参考了此处的字典:Does the Enumerator of a Dictionary<TKey, TValue> return key value pairs in the order they were added? 和此处:Dictionary enumeration order

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

此处是演示 ImmutableDictionary 中键的不同顺序的精简代码 sn-p(针对 .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 的问题的回答中所述:“Dictionary 确实以相同的顺序返回项目(假设您没有触发哈希表的调整大小)”。同样,我知道不应依赖当前的排序行为,但不清楚在什么情况下(例如,使用 .NET Framework、.NET Standard、.NET Core 时)执行之间的排序实际上不同。我的问题是:

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

【问题讨论】:

  • 真的想知道为什么吗?没有挖掘源代码并进行比较,我想最简单的答案是它们是不同的代码库。然而,除了行为之外,不应依赖其作为实现细节(如您所见)
  • 每天我走进商店都会看到按字母顺序排列的巧克力棒。我问店主他保证每天都做吗?他说不。第二天去问他们为什么今天不正常有意义吗?不——因为他明确表示他没有承诺。 说顺序不是确定性的全部意义在于避免您依赖它。回答您所问问题的危险在于您将依赖实施细节可能会改变
  • a Dictionary does return items in the same order (assuming that you don't trigger a resize of the hashtable)" 更准确的说法是a Dictionary does currently return items in the same order (assuming that you don't trigger a resize of the hashtable) in the current implementations and runtimes, but that may change in future".
  • They indicated that, despite enumeration order being "undefined", the order of enumeration is deterministic. 你表现得好像这两个陈述相互矛盾。 undefined 不代表random。这意味着I promise nothing。您说订单是确定性的——订单具体保证根据合同是确定性的。 如果您依赖订单,那么您做错了。合同明确规定“不要那样做”。
  • does anyone know which versions/platforms consistently return the same ordering between executions? 简短的回答是 - 他们都没有承诺这样做。因此,任何测试都应该根据合同编写,而不是观察到的行为。 Why does an ImmutableDictionary (in .NET Framework 4.7.2) return items in the same order between executions but an ImmutableDictionary (in .NET Core 3.1) consistently return items in a different order? 因为合同没有说它不能这样做。如果它是无序的,那么根据定义,它可以存储/排序/返回它自己。

标签: c# .net .net-core


【解决方案1】:

因为 .NET Core 中“字符串”的哈希函数是不确定的。

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

您可以阅读更多关于它的信息here

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

根据您的情况,您可以尝试切换到一种类型,其中您可以通过类型本身(例如 int)或提供具有自定义散列函数的类型来获得确定性散列函数。

虽然在原始问题中有一个后续问题 - 为什么 Dictionary 确定性枚举,但 ImmutableDictionary 不确定性枚举,如果两者都键入字符串,并且字符串生成不同每个应用程序执行的哈希值。

这里的答案是枚举器如何为每种类型工作。对于Core 中的Dictionary,基本上有两个集合,散列和条目(参见article 中的图表)。 Dictionary 的枚举使用条目,并且条目大致按照它们添加的顺序出现,因此它与散列函数无关。枚举器代码可以在 Dictionaryhere 的 KeyCollection 的自定义枚举器中看到。

但是对于 ImmutableDictionary,枚举遵循哈希(请参阅在 ImmutableDictionary 中调用的 HashBucket.Enumerator)。所以在框架中,字符串一致地散列,一切都很好,散列保留了它们的顺序。但现在在 Core 中,使用字符串键,每次运行的哈希值都不同,它们评估到不同的位置,因此它们的顺序不同。

希望能涵盖它。

【讨论】:

  • 或者你不能依赖未定义的行为。
  • 这是一个很好的答案!谢谢你。但是,为什么它也不会影响Dictionary?也就是说,如果我从 ImmutableDictionary 更改为 Dictionary(稍作修改),则在执行之间会保留顺序。
  • In .NET Framework the same strings generated the same hash codes on each execution, so their order always remained the same during enumeration. 您是如何得出哈希影响排序的结论的?那是保证吗? 我有兴趣了解更多关于此的信息。
  • @mjwills 您可以运行原始问题中的示例代码。为 .NET 编译时多次执行该代码每次都会产生相同的输出。在 .NET Core 中,每次都是不同的顺序。最重要的是,如果您在 .NET Framework 中尝试类似“a”.GetHashCode() 的操作,多个应用程序运行将给您相同的哈希码。在 .NET Core 中多次执行会给出不同的哈希码。最后,如果您使用具有确定性哈希码作为键类型的类型,则无论您使用哪种 .NET,顺序都保持不变。
  • @mjwillis 这是基于行为的。我同意依赖字典的顺序是错误的,它没有被合同定义。但是问题仍然存在,为什么框架之间存在实现差异,以及是什么导致了这些差异。更多的是好奇,是什么使框架实现在 Core 中具有确定性和非确定性。弄清楚里面发生了什么很有趣。这些东西。
【解决方案2】:

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

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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    • 2016-01-27
    • 2022-12-03
    • 2021-06-13
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多