Dictionary 是否损坏或者 GetHashCode() 应该仅基于不可变成员?
将对象添加到 .NET System.Collections.Generic.Dictionary class key的hashcode在内部存储并用于以后的比较。当哈希码在首次插入字典后发生变化时,它通常会变得“不可访问”,并且当存在检查(即使使用相同的引用)返回 false 时,可能会让用户感到惊讶(下面的示例代码)。
GetHashCode 文档说:
只要确定对象 Equals 方法返回值的对象状态没有发生修改,对象的 GetHashCode 方法就必须始终返回相同的哈希码。
因此,根据 GetHashCode
文档,哈希码可能会在 相等 - 确定状态已更改,但 Dictionary
实现不支持此操作。
当前的 .NET 字典实现是否因错误地忽略哈希码限额而被破坏? GetHashCode()
应该仅基于不可变成员吗?或者,是否有其他方法可以打破可能的错误二分法?
class Hashable
{
public int PK { get; set; }
public override int GetHashCode()
{
if (PK != 0) return PK.GetHashCode();
return base.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as Hashable);
}
public virtual bool Equals(Hashable other)
{
if (other == null) return false;
else if (ReferenceEquals(this, other)) return true;
else if (PK != 0 && other.PK != 0) return Equals(PK, other.PK);
return false;
}
public override string ToString()
{
return string.Format("Hashable {0}", PK);
}
}
class Test
{
static void Main(string[] args)
{
var dict = new Dictionary<Hashable, bool>();
var h = new Hashable();
dict.Add(h, true);
h.PK = 42;
if (!dict.ContainsKey(h)) // returns false, despite same reference
dict.Add(h, false);
}
}
When an object is added to the .NET System.Collections.Generic.Dictionary class the hashcode of the key is stored internally and used for later comparisons. When the hashcode changes after its initial insertion into the dictionary it often becomes "inaccessible" and may surprise its users when an existence check, even using the same reference, returns false (sample code below).
The GetHashCode documentation says:
The GetHashCode method for an object must consistently return the same hash code as long as there is no modification to the object state that determines the return value of the object's Equals method.
So, according to the GetHashCode
docs, the hashcode may change whenever equality-determining state is changed, yet the Dictionary
implementation does not support this.
Is the current .NET dictionary implementation broken in that it incorrectly ignore the hashcode allowances? Should GetHashCode()
only be based on immutable members? Or, is there something else to break a possible false dichotomy?
class Hashable
{
public int PK { get; set; }
public override int GetHashCode()
{
if (PK != 0) return PK.GetHashCode();
return base.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as Hashable);
}
public virtual bool Equals(Hashable other)
{
if (other == null) return false;
else if (ReferenceEquals(this, other)) return true;
else if (PK != 0 && other.PK != 0) return Equals(PK, other.PK);
return false;
}
public override string ToString()
{
return string.Format("Hashable {0}", PK);
}
}
class Test
{
static void Main(string[] args)
{
var dict = new Dictionary<Hashable, bool>();
var h = new Hashable();
dict.Add(h, true);
h.PK = 42;
if (!dict.ContainsKey(h)) // returns false, despite same reference
dict.Add(h, false);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不,您只是不应该在将键插入字典后对其进行变异(以实质性方式)。这是设计使然,也是我使用过的每个哈希表的工作方式。该文档甚至指定了这一点:
所以这只会让不阅读文档的用户感到惊讶:)
No, you just shouldn't mutate a key (in a material way) after inserting it into a dictionary. This is by design, and the way that every hash table I've ever used works. The docs even specify this:
So it's only going to surprise users who don't read documentation :)
为了补充乔恩的答案,我只想强调您引用的文档的某个部分:
现在,你就违反了规则。您更改了
PK
,这不会影响Equals
的结果(因为您在那里签入了ReferenceEquals
),但是GetHashCode
的结果确实发生了变化。这就是简单的答案。采用更概念化的方法,我认为您可以这样看待它:如果您覆盖您的类型的
Equals
和GetHashCode
行为,那么您就已经掌握了这种类型的一个实例与另一个实例相等的含义的概念。事实上,您已经以一种可以将Hashable
对象更改为完全不同的东西的方式来定义它;即,不再像以前那样使用的东西(因为它的哈希码已经改变)。从这个角度考虑,在执行
dict.Add(h, true)
之后,然后更改h.PK
,字典不会 不再包含h
引用的对象。它包含一些其他的东西(实际上并不存在于任何地方)。这有点像你定义的类型是一条已经蜕皮的蛇。To add to Jon's answer, I would just add emphasis to a certain part of the docs that you quoted:
Now, right there you've broken the rules. You changed
PK
, which doesn't affect the outcome ofEquals
(because you have thatReferenceEquals
check in there), but the result of yourGetHashCode
does change. So that's the simple answer.Taking the more conceptual approach, I think you can look at it like this: if you have overridden the
Equals
andGetHashCode
behavior for your type, then you've taken ownership of the concept of what it means for one instance of this type to be equal to another. And in fact you have defined it in a way that aHashable
object can be changed into something completely different; i.e., something that is no longer usable in the same way it was previously (because its hash code has changed).Considered from this perspective, after you do
dict.Add(h, true)
, and then you changeh.PK
, the dictionary doesn't contain the object referenced byh
anymore. It contains something else (which doesn't really exist anywhere). It's kind of like the type you've defined is a snake that has shed its skin.