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
使用指定的 IEqualityComparer
为 HashSet
生成了哈希码(它根据其中的元素生成哈希码 - 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
并传入带有 Green
、Red
和 Blue
的新 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。
拜托,有人可以帮我写代码吗?
我不明白为什么 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
使用指定的 IEqualityComparer
为 HashSet
生成了哈希码(它根据其中的元素生成哈希码 - 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
并传入带有 Green
、Red
和 Blue
的新 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。