如何为复杂相等性实现Object.GetHashCode()方法?

10

基本上,到目前为止我有以下内容:

class Foo {
    public override bool Equals(object obj)
    {
        Foo d = obj as Foo ;
        if (d == null)
            return false;

        return this.Equals(d);
    }

    #region IEquatable<Foo> Members

    public bool Equals(Foo other)
    {
        if (this.Guid != String.Empty && this.Guid == other.Guid)
            return true;
        else if (this.Guid != String.Empty || other.Guid != String.Empty)
            return false;

        if (this.Title == other.Title &&
            this.PublishDate == other.PublishDate &&
            this.Description == other.Description)
            return true;

        return false;
    }
}

所以,问题是这样的:我有一个非必填字段 Guid,它是唯一标识符。如果没有设置它,那么我需要尝试使用不太准确的指标来确定两个对象是否相等。这可以正常工作,但会使 GetHashCode() 混乱...我该怎么做?一个天真的实现可能是这样的:

public override int GetHashCode() {
    if (this.Guid != String.Empty)
        return this.Guid.GetHashCode();

    int hash = 37;
    hash = hash * 23 + this.Title.GetHashCode();
    hash = hash * 23 + this.PublishDate.GetHashCode();
    hash = hash * 23 + this.Description.GetHashCode();
    return hash;
}

但是这两种哈希发生冲突的机会有多大呢?我认为不可能是1 in 2 ** 32。这是个坏主意吗?如果是,应该怎么做?


1
重要的是你的哈希算法与相等算法一致,而不是分布均匀。记住,哈希的目的仅仅是为了在哈希表中获得一个合理的分布;只要你不是严重偏向于一个特定的桶,那么你很可能会没问题。如果你担心,可以选择一个消费者可能遇到的合理场景——比如将几百个对象放入字典中——并进行一些性能测试,以查看是否获得可接受的结果。 - Eric Lippert
我在实际使用中看到的最多的是约200个,但典型的使用量是小于30个,所以你可能是对的。 - Matthew Scharley
1
在不到30个项目的情况下,使用链表进行线性搜索可能是相当高效的。你可以总是返回零的哈希码,有100%的碰撞几率,但仍然能够获得可接受的性能。拥有良好分布的哈希码的目的是使性能在字典大小变大时扩展。如果你只打算将少量项目放入表中,即使分布很差,仍然可以获得良好的结果。 - Eric Lippert
2个回答

10

自定义类的哈希码方法非常简单,只需将每个字段的哈希码进行按位异或即可。代码如下:

int hash = 0;
hash ^= this.Title.GetHashCode();
hash ^= this.PublishDate.GetHashCode();
hash ^= this.Description.GetHashCode();
return hash;

来自以上链接:

XOR具有以下良好的属性:

  • 它不依赖于计算顺序。
  • 它不会“浪费”位。如果在其中一个组件中改变了一个位,最终值将发生变化。
  • 它很快,在即使是最原始的计算机上也只需一个周期。
  • 它保持均匀分布。如果你合并的两个部分是均匀分布的,那么组合也是如此。换句话说,它不倾向于将摘要范围压缩到更窄的带宽中。

如果您期望在字段中有重复的值,XOR将无法很好地工作,因为重复的值在进行XOR时会互相抵消。由于在这种情况下需要散列三个不相关的字段,所以这不应该成为问题。


7
异或运算不依赖于计算顺序,这是一把双刃剑......如果你有多个字段类型相同的对象(例如两个日期),当它们在对象之间交换时,哈希函数将无法区分它们。 - jerryjvl

5
我认为你所选择的方法没有问题。过于担心哈希冲突通常表明过度思考问题; 只要哈希高度可能是不同的,就应该没问题。
最终,如果可以合理地期望大多数对象可以根据其标题和发布日期(例如书籍)进行区分,甚至可以考虑从哈希中省略Description
你甚至可以考虑完全忽略哈希函数中的GUID,并仅在Equals实现中使用它来消除哈希冲突的不太可能的情况。

尽管如此,显然,如果GUID存在,则很可能比任意的标题字符串更快地哈希...因此它可以成为可行的性能优化。 - jerryjvl
描述需要包含在相等性检查中(因此也包括哈希码)中。 - Matthew Scharley
哦,顺便说一下,RSS条目。 - Matthew Scharley
请注意,'Description' 在 'Equals' 中存在是完全有效的,但在 'GetHashCode' 中不存在是可以的... 只要相等项的哈希值永远不会不同,您的哈希函数就可以比相等性测试弱。 - jerryjvl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接