【问题标题】:What is the correct implementation for GetHashCode() for entity classes?实体类的 GetHashCode() 的正确实现是什么?
【发布时间】:2010-10-05 05:17:30
【问题描述】:

下面是一个为应用程序中的所有其他实体派生的实体基类重写 Object.Equals() 的示例实现。

所有实体类都有属性 Id,它是一个可为空的 int。 (它是实体类对应的任何表的主键。)

public override bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
                return false;

            if (base.Equals(obj))
                return true;

            return Id.HasValue && ((EntityBase) obj).Id.HasValue &&
                   Id.Value == ((EntityBase) obj).Id.Value;
        }

鉴于 Equals() 的这种实现,如何正确实现 GetHashCode()?

【问题讨论】:

  • 为了记录,为 ID 设置一个可为空的 int 是一个可怕的想法。 ID 实际上应该始终是 GUID,并且绝对不能为空。

标签: c# .net orm


【解决方案1】:

如果您从已经覆盖 GetHashCode 的东西派生出来,我会将其实现为:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 37;
        hash = hash * 23 + base.GetHashCode();
        hash = hash * 23 + Id.GetHashCode();
        return hash;
    }
}

Id 的空值将为 Id.GetHashCode() 返回 0。

如果你的类只是从 Object 派生,我会返回 Id.GetHashCode() - 你确实不想在你的哈希码中包含 object.GetHashCode 实现,因为它基本上最终成为对象身份。

请注意,如果两个实体都没有 Id,则您的相等定义不会返回 true,但两个对象都会返回相同的哈希码。您可能希望考虑更改您的 Equals 实现。

【讨论】:

  • 对于那些想知道的人,就像我一样:23 和 37 是互质的任意数字。乔恩在这里以类似的答案陈述了这一点:eggheadcafe.com/software/aspnet/29483139/…
  • @JonSkeet 你不同意添加base.GetHashCode() 会使用Object.GetHashCode,它使用内存地址。在那种情况下GetHashCode不具有当Equals为两个对象返回true时它们的hashCodes应该相同的属性??
  • @JonSkeet 我还要在哈希码实现中添加unchecked {} 以避免编译器选项...这是您可能永远不会遇到的边缘情况...无论哪种方式,未检查的关键字都是一种记录整数溢出在函数中是可以的方式。
  • @Japp:如果您直接从对象派生,那么您确实不想使用 base.GetHashCode。请参阅代码后的第二段。是的,使用 unchecked 是有意义的。
  • @JonSkeet 为什么不需要在代表对象特定类的哈希码中也插入特定值。我的意思是,这个实现导致同一个基类的两个不同子类的两个不同对象返回相同的哈希码。我们不应该阻止这种情况,还是没有理由阻止这种情况? (我知道在字典中,检查getHashCode 之后,评估Equals 方法会有所不同)
【解决方案2】:

将类型用作哈希码的一部分怎么样?
这会是一个好的实现吗?

public class Foo
{
    public int Id { get; set; }

    // other properties here
    // ......

    public override int GetHashCode()
    {
        int hash = 37;
        hash = hash * 23 + typeof(Foo).GetHashCode();
        hash = hash * 23 + Id.GetHashCode();
        return hash;
    }
}

【讨论】:

    【解决方案3】:

    Jon Skeet 的回答是一个很好的解决方案,但是,您可能需要添加一个未经检查的代码块以允许整数溢出

    unchecked
    {
      int hash = ...;
      return hash
    }
    

    https://msdn.microsoft.com/en-us/library/khy08726(v=vs.140).aspx

    如果既没有指定checked也没有指定unchecked,则默认上下文取决于编译器选项等外部因素。

    我还想再次补充一点,在 POCO 上使用 base.GetHashCode() 将调用默认的 object.GetHashCode。这绝对不是你想要的……

    【讨论】:

    • unchecked 是默认值,不需要。
    • "如果既没有指定checked也没有指定unchecked,默认上下文取决于编译器选项等外部因素。" msdn.microsoft.com/en-us/library/khy08726(v=vs.140).aspx 所以如果你写这样的代码,我会把它放在那里。它也是一种文档形式。
    • 已将未选中的部分添加到我自己的答案中,顺便说一句。
    【解决方案4】:

    如果Id 属性在实例的生命周期内(或至少在需要使用其哈希值的时间,例如当对象在地图中或其他需要哈希的集合)。

    假设是这样,您可以只使用Id 的值作为所有有效值的哈希值,然后对空值使用固定哈希值。我不记得最合适的是什么,但我会假设一个随机选择的 null 值(在编译之前随机选择,而不是在运行时)或有效Id 值的中值(即介于 0 和int.Max)。

    【讨论】:

    • 它不必在整个生命周期内都是不可变的 - 只需从它第一次插入地图或其他任何地方的那一刻起。可以创建一个实例,搞乱它,把它放到地图中,然后然后停止搞乱它。
    猜你喜欢
    • 2014-11-08
    • 2019-07-16
    • 2014-11-04
    • 2016-03-03
    • 1970-01-01
    • 1970-01-01
    • 2011-11-06
    • 2018-08-12
    • 1970-01-01
    相关资源
    最近更新 更多