当一个对象被添加到.NET System.Collections.Generic.Dictionary类时,该密钥的哈希码被内部存储并用于以后的比较。当哈希码最初插入字典后发生变化时,它常常变得“无法访问”,并且当存在检查时,即使使用相同的引用,也会使其用户感到惊讶,返回false(下面的示例代码)。
GetHashCode文档说:
对象的GetHashCode方法必须始终返回相同的哈希码,只要不对对象状态进行修改即可确定对象的Equals方法的返回值。
因此,根据GetHashCode
文档,只要equality - 确定状态发生更改,哈希码就可能会更改,但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);
}
}
答案 0 :(得分:27)
不,你不应该在将密钥插入字典后改变密钥(以物质方式)。这是设计,以及我曾经使用的每个哈希表的工作方式。文档甚至指定了这个:
只要将对象用作
Dictionary<TKey, TValue>
中的键,就不得以任何影响其哈希值的方式进行更改。根据字典的相等比较器,Dictionary<TKey, TValue>
中的每个键必须是唯一的。键不能为null,但如果值类型TValue是引用类型,则值可以是。
因此,只会让那些不阅读文档的用户感到惊讶:)
答案 1 :(得分:9)
要添加Jon的答案,我只想强调你引用的某些文档:
对象的GetHashCode方法 必须始终返回相同的哈希值 代码只要没有 修改对象状态 确定对象的Equals方法的返回值。
现在,你已经违反了规则。您更改了PK
,其中不影响Equals
的结果(因为您在那里进行了ReferenceEquals
检查),但GetHashCode
的结果是Equals
1}} 更改。这就是简单的答案。
采用更具概念性的方法,我认为你可以这样看:如果你的覆盖你的类型的GetHashCode
和Hashable
行为,那么你'已经取得了概念的所有权,这对于这种类型的一个实例来说意味着与另一个相等。实际上,您已经以dict.Add(h, true)
对象可以将更改为完全不同的的方式定义了它;即,某些东西不再像以前那样可用(因为它的哈希码已经改变了)。
从这个角度考虑,执行h.PK
后,然后更改h
,字典不包含{{1}}引用的对象了。它包含 else (在任何地方都不存在)。它有点像你定义的类型是一条已经脱落皮肤的蛇。