Dictionary 是否损坏或者 GetHashCode() 应该仅基于不可变成员?

发布于 2024-10-15 08:38:53 字数 1765 浏览 5 评论 0原文

将对象添加到 .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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浮世清欢 2024-10-22 08:38:53

不,您只是不应该在将键插入字典后对其进行变异(以实质性方式)。这是设计使然,也是我使用过的每个哈希表的工作方式。该文档甚至指定了这一点:

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

所以这只会让不阅读文档的用户感到惊讶:)

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:

As long as an object is used as a key in the Dictionary<TKey, TValue>, it must not change in any way that affects its hash value. Every key in a Dictionary<TKey, TValue> must be unique according to the dictionary's equality comparer. A key cannot be null, but a value can be, if the value type TValue is a reference type.

So it's only going to surprise users who don't read documentation :)

南汐寒笙箫 2024-10-22 08:38:53

为了补充乔恩的答案,我只想强调您引用的文档的某个部分:

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

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

采用更概念化的方法,我认为您可以这样看待它:如果您覆盖您的类型的EqualsGetHashCode行为,那么您就已经掌握了这种类型的一个实例与另一个实例相等的含义的概念。事实上,您已经以一种可以将 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:

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.

Now, right there you've broken the rules. You changed PK, which doesn't affect the outcome of Equals (because you have that ReferenceEquals check in there), but the result of your GetHashCode 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 and GetHashCode 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 a Hashable 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 change h.PK, the dictionary doesn't contain the object referenced by h 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文