警告: "... 覆盖了Object.Equals(object o)但没有覆盖Object.GetHashCode()"

34

我覆盖了我的类的Equals()方法,以比较类型为Guid的ID值。

然后Visual Studio警告:

... 覆盖了Object.Equals(object o)但未覆盖Object.GetHashCode()

所以我也覆盖了它的GetHashCode()方法,像这样:

public partial class SomeClass
{
    public override bool Equals(Object obj)
    {
        //Check for null and compare run-time types.
        if (obj == null || this.GetType() != obj.GetType()) return false;

        return this.Id == ((SomeClass)obj).Id;
    }

    public override int GetHashCode()
    {
        return this.Id.GetHashCode();
    }
}

看起来它能够工作。 我做得对吗?请记住Id是Guid类型。我的类是否为Entity Framework对象有关系吗?

5个回答

38

正如其他人所说,Equals中使用反射似乎有些可疑。抛开这个问题,我们来关注一下GetHashCode。

GetHashCode的主要规则是如果两个对象相等,它们必须具有相同的哈希码,或者等价地说,如果两个对象具有不同的哈希码,则它们必须是不相等的。在这方面,你的实现看起来很好。

你可以违反反之规则,也就是说,如果两个对象具有相同的哈希码,则它们可以相等或不等,取决于你的需要。

我假设“Id”是一个不可变属性。如果“Id”在对象的生命周期中可以更改,则将对象放入哈希表时可能会出现问题。考虑确保只使用不可变属性来计算相等和哈希码。

你的实现看起来很好,但是你提出这个问题表明你可能没有很好地掌握构建GetHashCode实现的所有微妙因素。开始的好地方是我的这篇文章:

http://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/


2
我在你的帖子中看到了这个,但似乎很难确保合同不被违反。 - crush
2
我非常希望看到更多有关在Equals中使用Reflection的不稳定性的讨论,特别是考虑到它在Object.Equals方法的官方文档的示例中被使用(例如,在该页面上的“Rectangle”类中)。 - kmote
2
@kmote:这是一个问答网站,不是讨论网站。如果你能用一个明确的问题形式表达你的疑虑,并且这个问题有一个确定的答案,那么请把问题发布为一个问题并看看大家有什么回答。 - Eric Lippert
3
@T.E.D .:这取决于你对“正常”是什么意思。它满足合法哈希码的最低要求。相等的对象具有相等的哈希码,因为所有对象都具有相等的哈希码。当对象发生变化时,哈希码不会被改变,因为哈希码是一个常量。由于哈希码永远不会改变,所以对象在集合中时也永远不会改变。然而,在平衡哈希表的目的方面,它失败了。所有东西都将被排序到同一个存储桶中,所以还不如使用列表! - Eric Lippert
4
@T.E.D.:你的问题是“如果我不调用GetHashCode,那么我如何实现它并不重要?” 好吧,我想不是很重要。但是,如果一个类型实现了相等性,那么可以合理地假设它可能被用于LINQ查询的联接子句中,此时你需要处理哈希表。大多数类型的作者无法选择实例是否会被放入哈希表中,因此没有必要向这些作者提供指导。 - Eric Lippert
显示剩余4条评论

8

我认为这看起来是正确的。每当我做类似的事情时,我通常也会实现IEquatable,以便同一编译时类型的变量之间的比较更加高效。

public partial class SomeClass : IEquatable<SomeClass>
{
     public override bool Equals(Object obj)
     {
         return Equals(obj as SomeClass);
     }
     public bool Equals(SomeClass obj)
     {
         if (obj == null) 
             return false;
         return Id == obj.Id;
     }
     public override int GetHashCode()
     {
         return Id.GetHashCode();
     }
} 

这种结构还允许拥有相同 Id 的更多衍生对象与较少衍生对象进行比较时视为相等。如果这不是期望的行为,则必须像在问题中一样同时比较类型。

if (obj.GetType() != typeof(SomeClass)) return false;

6

由于你没有使用密封类,我建议不要像这样检查类的相等性:this.GetType() != obj.GetType()。任何SomeClass的子类也应该能够参与Equals,所以你可能需要使用以下代码:

if (obj as SomeClass == null) return false;

子类实例何时才会等于基类实例?子类将具有额外的属性,当进行比较时,基类不知道这些属性。 - Joe White
3
Equals 目前只关注 Id,这是所有子类都会拥有的。 - recursive
我承认你的逻辑,但正如我在对Eric的评论中指出的那样,OP的Equals代码直接来自于官方MSDN文档中的一个示例(请参见Point类示例)。 - kmote

6
传统上,Equals的实现方式是只有在两个对象在每个方面都完全相同的情况下才会被视为“相等”。例如,如果您有两个表示数据库中同一对象的对象,但其中一个具有不同的Name属性,则这些对象不被认为是“相等”的,并且应尽可能避免生成相同的“Hashcode”。
更好的做法是,宁愿出现“不相等”的情况,也不要冒险调用不相等的两个对象。这就是为什么对象的默认实现使用对象本身的内存位置:除非它们是完全相同的对象,否则永远不会认为两个对象是“相等”的。因此,我认为,除非您想编写检查它们所有属性的相等性的GetHashCodeEquals方法,否则最好不要覆盖这两种方法。
如果您有一个数据结构(如HashSet),需要根据ID值确定相等性,可以为该数据结构提供特定的IEqualityComparer实现。

5
你第一个问题得到了出色的答案:

我做得对吗?

现在我来回答你的第二个问题:

我的类是Entity Framework对象,这会有影响吗?

非常有影响。Entity Framework内部经常使用HashSet。例如动态代理用HashSet表示集合导航属性,而EntityObject使用EntityCollection,后者又在内部使用HashSet

1
因此,除非您了解Entity Framework期望哈希码的行为方式,否则请勿操纵哈希码。 - StriplingWarrior
1
我一直在想,鉴于你的回答,OP(原帖人)的实现是否需要修改。你说它很重要,以及为什么重要,但并未说明对于GetHashCode()的实现者来说这意味着什么。能不能请您提供更多的信息? - Andrew
@Andrew:相关信息已经在其他答案中提供了。OP的实现不需要任何更改。 - Ladislav Mrnka

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