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

Kal*_*son 12 .net c# dictionary hashcode immutability

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

GetHashCode的文件说:

为对象GetHashCode方法必须一致,只要没有修改确定的该对象的Equals方法返回值的对象的状态返回相同的散列码.

因此,根据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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 27

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

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

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


Dan*_*Tao 9

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

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

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

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

从这个角度考虑,在你做完dict.Add(h, true)之后再进行更改时h.PK,字典不再包含被引用的对象h.它包含其他东西(在任何地方都不存在).它有点像你定义的类型是一条脱落皮肤的蛇.