ContainsKey for Dictionary with HashSet keys unexpectedly returns false

ContainsKey for Dictionary with HashSet keys unexpectedly returns false

拜托,有人可以帮我写代码吗?

我不明白为什么 ContainsKey returns false.

var dictionary = new Dictionary<HashSet<string>, string>(HashSet<string>.CreateSetComparer())
    {{new HashSet<string> {"Red", "Green"}, "Color"}};

var shouldBeTrue = dictionary.ContainsKey(new HashSet<string> {"Green", "Red"}); //true

var first = dictionary.Keys.First();
first.Add("Blue");

var shouldBeTrueButIsNot = dictionary.ContainsKey(new HashSet<string> {"Green", "Red", "Blue"}); //false
var shouldBeAlsoTrueButIsNot = dictionary.ContainsKey(first); //false
var referenceEquals = dictionary.Any(k => k.Key == first); //true
var equals = dictionary.Any(k => k.Key.Equals(first)); //true

因此,您创建了一个 HashSet 并将其插入 Dictionary。在幕后,Dictionary 使用指定的 IEqualityComparerHashSet 生成了哈希码(它根据其中的元素生成哈希码 - Red & Green 在这种情况下。

然后 Dictionary 使用上面生成的散列代码将 HashSet 存储在其内部散列 table 中。为了有一个实用的例子,让我们假设它生成的哈希码是 123.

然后您通过添加新元素突变 现有 HashSet。更重要的是,它 仍然使用 old 哈希码 (123).

存储在 Dictionary

当您对 Dictionary 执行查找时(在本例中使用 ContainsKey),Dictionary 首先为查找记录生成哈希码,并为每个匹配record in its internal hash table (yes, may be multiple) 然后它将调用这些记录上的 Equals 方法以找到 'actual' 匹配元素。

那么为什么这一切很重要?

好吧,如果您还记得的话,您的初始 HashSet 的哈希码 是根据当时其中的元素生成的 。所以在这个例子中 Red & Green 的散列码最终是 123 。如果在 添加新值后,在 HashSet 上调用比较器的 GetHashCode,则哈希码将不再是 123 ,例如,假设它实际上是 999.

因此,当您使用 ContainsKey 查询 Dictionary 并传入带有 GreenRedBlue 的新 HashSet 时,生成它的哈希码 (999) 并在 Dictionary's 内部哈希 table 上完成查找 -> oops 没有找到哈希码 999 的记录.

证明方法如下:

Dictionary 中删除条目,然后在执行查找之前重新添加它。 所以代码看起来像

        ...
        var first = dictionary.Keys.First();

        dictionary.Remove(first); // Remove the record before mutating it so it can be found using its hash code
        first.Add("Blue");
        dictionary.Add(first, "Color"); // Re-add it, triggering the generation of a new hash code
        
        var shouldBeTrueButIsNot = dictionary.ContainsKey(new HashSet<string> {"Green", "Red", "Blue"}); //false
        var shouldBeAlsoTrueButIsNot = dictionary.ContainsKey(first);
        var referenceEquals = dictionary.Any(k => k.Key == first);
        var equals = dictionary.Any(k => k.Key.Equals(first));
        ...

当您使用上述更改测试代码时,您将获得预期的结果。另外,上面的代码纯粹是为了演示这个问题——不建议在实践中使用。

我强烈建议您阅读评论中提到的 docs