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

时间:2011-02-01 22:57:28

标签: c# .net dictionary immutability hashcode

当一个对象被添加到.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);
    }
}

2 个答案:

答案 0 :(得分:27)

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

  

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

因此,只会让那些不阅读文档的用户感到惊讶:)

答案 1 :(得分:9)

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

  

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

现在,你已经违反了规则。您更改了PK,其中影响Equals的结果(因为您在那里进行了ReferenceEquals检查),但GetHashCode的结果是Equals 1}} 更改。这就是简单的答案。

采用更具概念性的方法,我认为你可以这样看:如果你的覆盖你的类型的GetHashCodeHashable行为,那么你'已经取得了概念的所有权,这对于这种类型的一个实例来说意味着与另一个相等。实际上,您已经以dict.Add(h, true)对象可以将更改为完全不同的的方式定义了它;即,某些东西不再像以前那样可用(因为它的哈希码已经改变了)。

从这个角度考虑,执行h.PK后,然后更改h,字典包含{{1}}引用的对象了。它包含 else (在任何地方都不存在)。它有点像你定义的类型是一条已经脱落皮肤的蛇。