字典是否被破坏或GetHashCode()仅基于不可变成员?

将对象添加到.NET System.Collections.Generic.Dictionary类时,密钥的哈希码将在内部存储并用于以后的比较 . 当哈希码在其初始插入字典后发生变化时,它常常变为"inaccessible",并且当存在检查(即使使用相同的引用)返回false时,可能会使其用户感到惊讶(下面的示例代码) .

GetHashCode文档说:

对象的GetHashCode方法必须始终返回相同的哈希码,只要对对象状态没有修改即可确定对象的Equals方法的返回值 .

因此,根据 GetHashCode 文档,只要equality -determining状态发生更改,哈希码就可能会更改,但 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);
    }
}

回答(2)

2 years ago

不,你不应该在将它插入字典后改变键(以物质方式) . 这是设计,以及我曾经使用的每个哈希表的工作方式 . 文档甚至指定了这个:

只要对象在Dictionary <TKey,TValue>中用作键,就不能以任何影响其哈希值的方式进行更改 . 根据字典的相等比较器,Dictionary <TKey,TValue>中的每个键必须是唯一的 . 如果值类型TValue是引用类型,则键不能为null,但值可以为 .

所以它只会给那些不阅读文档的用户带来惊喜:)

2 years ago

要添加Jon的答案,我只想强调你引用的文档的某个部分:

对象的GetHashCode方法必须始终返回相同的哈希码,只要对对象状态没有修改即可确定对象的Equals方法的返回值 .

现在,就在那里,你违反了规则 . 您更改了 PK ,这不会影响 Equals 的结果(因为您在那里检查了 ReferenceEquals ),但 GetHashCode 的结果确实发生了变化 . 这就是简单的答案 .

采用更概念化的方法,我认为你可以这样看:如果你已经覆盖了你的类型的 EqualsGetHashCode 行为,那么你已经拥有了对于这种类型的一个实例意味着什么的概念 . 等于另一个 . 事实上,你已经以一种 Hashable 对象可以变成完全不同的方式来定义它;即,某些东西不再像以前那样可用(因为它的哈希码已经改变) .

从这个角度考虑,在你执行 dict.Add(h, true) ,然后你改变 h.PK 之后,字典 doesn't 再包含 h 引用的对象 . 它包含了其他的东西(它没有像你定义的类型那样是一条脱掉它皮肤的蛇 .