1

在 .NET 中,您需要 Equals(object) 和 GetHashCode() 兼容。但有时你不能:

public class GreaterThan32Bits
{
    public int X { get; set; }
    public int Y { get; set; }
}

因为数据密度大于 32 位,并且 GetHashCode 返回一个 Int32,所以您将有 3 个解决方案(假设正确实现了 GetHashCode):

  1. 避免代码重复 因为不正确而被丢弃

    public override bool Equals(object other)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;
        return this.GetHashCode() == other.GetHashCode();
    }
    
  2. 与 GetHashCode() 分开实现 Equals

    public override bool Equals(object obj)
    {
        if(ReferenceEquals(null, other)) return false;
        if(ReferenceEquals(this, other)) return true;
        var other = obj as GreaterThan32Bits;
        if(this.X == other.X) return this.Y == other.Y;
        return false;
    }
    
  3. 实现精度更高的GetHashCode64,覆盖的GetHashCode(32位)会返回(int)GetHashCode64(),Equals会返回this.GetHashCode64() == other.GetHashCode64()

你会实施哪一个?

第一个解决方案不精确,但更清洁。第二个选项看起来很干净,但是当类具有更多属性时会变得非常复杂。第三种选择是妥协。

4

4 回答 4

5

要求如下: if (a.Equals(b)), then a.GetHashCode() == b.GetHashCode()

不是反过来。

你永远不应该用 GetHashCode() 来实现 Equals()。GetHashCode 发生冲突是完全有效的,但 Equals() 不能返回误报。

我建议这个实现:

public override int GetHashCode()
{
    return unchecked( this.X * p1 + this.Y * p2 );
}

public override bool Equals(object obj) 
{
    var other = obj as GreaterThan32Bits;
    // you must do the null test after the cast, otherwise the
    // function crashes when obj is not a GreaterThan32Bits instance
    if (ReferenceEquals(other, null)) return false;
    return this.X == other.X && this.Y == other.Y;
}

其中 p1 和 p2 是大素数。这通常会产生一个好的散列函数(很少有散列冲突 -> 字典变得高效)。如果 X 和 Y 值是独立的(例如,您不会期望像 X=Y 这样的直线上有很多点),那么即使是简单的类似的东西X ^ Y也可以是一个很好的哈希函数。

但是话又说回来,如果您实际上将类用作字典(或其他哈希表)中的键,则只需要一个好的哈希函数。

事实上,在 GetHashCode() 中总是返回 0 并且只实现 Equals() 是完全可以的。字典仍然可以与键等对象正常工作,只是效率低下。

于 2009-07-20T23:29:14.720 回答
4

您的第一个实现正确。即使对象本身不相等,两个对象的哈希码也可能相等:这是哈希码的本质。

对象哈希码可能有助于确定两个对象何时相等,但要确定它们是否相等,您必须调用.Equals().

始终返回 0 的实现GetHashCode()是合法的,但当将该类型的对象插入到各种类型的容器中时可能效率不高。

您的选项 2 是最佳选择。将实现与Equals()分开是一个好主意GetHashCode(),因为它们做的事情完全不同。当且仅当两个对象在所有方面都相等时,Equals()必须返回。true为此,您通常必须单独检查每个对象属性。

于 2009-07-20T23:19:51.153 回答
2

严格来说,第一个解决方案不起作用。这不是一个解决方案。

散列的想法是完全不同的。Int32 就足够了。

建议的 GetHashCode() 是

return X ^ Y;

就这么简单。

编辑: Equals 方法然后可以使用 GetHashCode(),但仅在散列不同时返回 false。无论如何都需要深度比较。

于 2009-07-20T23:21:13.550 回答
1

我认为您缺少的关键是 GetHashCode() 不必返回唯一值。

两个不同的对象返回相同的 GetHashCode 是完全可以接受的。假设您将两个具有相同 HashCode 的对象添加到 HashSet,那么容器将首先使用 GetHashCode 来查找对象在 HashSet 中的大致位置,然后在所有匹配对象上使用等号来找到您的确切对象。

显然,如果每个对象都有唯一的哈希码,那就更好了。如果每个对象都返回相同的 hashCode,那么性能会很糟糕。

于 2009-07-20T23:19:50.467 回答