Equals和GetHashCode的最佳策略是什么?

时间:2010-03-02 12:46:32

标签: c# .net equals gethashcode

我正在使用域模型,并且正在考虑在.NET中实现这两种方法的各种方法。你最喜欢的策略是什么?

这是我目前的实施:

public override bool Equals(object obj)
{
    var newObj = obj as MyClass;

    if (null != newObj)
    {
        return this.GetHashCode() == newObj.GetHashCode();
    }
    else
    {
        return base.Equals(obj);
    }
}

// Since this is an entity I can use its Id
// When I don't have an Id, I usually make a composite key of the properties
public override int GetHashCode()
{
    return String.Format("MyClass{0}", this.Id.ToString()).GetHashCode();
}

7 个答案:

答案 0 :(得分:31)

Domain-Driven Design区分实体值对象。这是一个很好的区分,因为它指导你如何实现Equals。

实体如果ID彼此相等则相等。

值对象如果所有(重要)组成元素彼此相等则相等。

在任何情况下,GetHashCode的实现都应基于用于确定相等性的相同值。换句话说,对于实体,哈希码应该直接从ID计算,而对于值对象,它应该从所有组成值计算。

答案 1 :(得分:5)

这里没有任何答案真的让我感到满意。既然您已经说过不能使用Id进行相等性,并且需要使用一组属性,那么这是一种更好的方法。注意:我不认为这总体上是实施EqualsGetHashCode的最佳方式。这是OP代码的更好版本。

public override bool Equals(object obj) {
   var myClass = obj as MyClass;

   if (myClass != null) {
      // Order these by the most different first.
      // That is, whatever value is most selective, and the fewest
      // instances have the same value, put that first.
      return this.Id == myClass.Id
         && this.Name == myClass.Name
         && this.Quantity == myClass.Quantity
         && this.Color == myClass.Color;
   } else {
      // This may not make sense unless GetHashCode refers to `base` as well!
      return base.Equals(obj);
   }
}

public override int GetHashCode() {
   int hash = 19;
   unchecked { // allow "wrap around" in the int
      hash = hash * 31 + this.Id; // assuming integer
      hash = hash * 31 + this.Name.GetHashCode();
      hash = hash * 31 + this.Quantity; // again assuming integer
      hash = hash * 31 + this.Color.GetHashCode();
   }
   return hash;
}

有关此背后的一些原因,请参阅this answer by Jon Skeet。使用xor并不好,因为各种数据集最终会导致相同的散列。这种带素数的环绕方法(上面的种子值为19和31,或者你选择的其他值)可以更好地分割成每个碰撞很少的“桶”。

如果您的任何值可以为null,我建议您仔细考虑它们应如何比较。您可以使用短路空值评估和空合并运算符。但是请确保如果null应该相等,那么当它们为null时,将不同的哈希代码分配给不同的可空属性。

另外,我不相信你的Equals实现有任何意义。比较两个对象的相等性时,首先比较它们的GetHashCode值。只有那些不同的是Equals方法运行(因此,如果散列到相同值的两个对象不同,则会检测到这个)。由于您的GetHashCode实施没有引用base,因此您的Equals方法可能没有任何意义。具体来说,如果Equals对于哈希码不同的两个对象都可以返回true,那么你将有一个严重的错误等待破解。

答案 2 :(得分:2)

Hashcodes可能会发生碰撞,所以我认为它们不是比较平等的好方法。您应该比较使对象“相等”的基础值。如果您的相等性包含多个属性,请参阅@Jon Skeet对此问题的回答:What is the best algorithm for an overridden System.Object.GetHashCode?以获得更好的GetHashCode实现。如果它只是一个属性,你可以只重用它的哈希码。

答案 3 :(得分:1)

假设实例相等,因为哈希码相等是错误的。

我猜你的GetHashCode实现没问题,但我通常使用类似的东西:

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}

答案 4 :(得分:1)

我偶然发现了这个老问题,恕我直言,我没有找到任何答案清楚简单地说明@tucaz提出的原始问题。

我同意上面(或下面:D)分享的许多考虑因素,但遗漏了“问题点”(我认为)。

提供:

  • 实体需要平等
  • 如果实体对象映射相同的实体,则可以认为它们是等于的,id est它们指的是相同的«实体密钥»
  • @tucaz所示的例子只是提到了一个«Id»(参见过度实现的GetHashCode())...更不用说错误 Equals(...)

我猜可以直接实现一个:

public class MyEntity: IEquatable<MyEntity> {
    int Id;

    public MyEntity(int id){
        Id = id;
    }

    public override bool Equals(object obj) => Equals(obj as MyEntity);
    public bool Equals(MyEntity obj) => obj != null && Id == obj.Id;
    public override int GetHashCode() => Id;
}

这就是全部!

答案 5 :(得分:1)

除了答案(不允许写评论)外,我想指出Visual Studio可以自动生成Equals和GetHashCode。 查看此答案:Is there a way to automatically generate equals and hashcode method in Visual Studio 我真的在寻找该自定义实现,而在这里找不到。

我也想链接这个问题: Best way to compare two complex objects 它与嵌套类结构有关。在注释的下方,可以找到带有Enumerations(例如List)的嵌套类结构的情况。

答案 6 :(得分:0)

我想根据上面的答案和我自己的经验来看一些特定的情况。

经验法则是,具有不同哈希码的两个实例应始终不相等,但如果哈希值相同,则它们可能相等也可能不相等。 GetHashCode()用于快速区分实例,Equals()用于验证相等性(对您而言意味着什么)。

还有许多内置机制正在寻找IEquatable<T>的实现,因此,最好声明实际上执行检查的Equals(MyClass)的替代。

具有唯一ID的类

考虑一个具有唯一ID的类。然后equals操作将只检查ID。哈希也是如此,仅依赖于ID。

public class IdClass : IEquatable<IdClass>
{
    public int ID { get; } // Assume unique
    public string Name { get; }


    #region IEquatable Members
    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(IdClass)</code></returns>
    public override bool Equals(object obj)
    {
        if (obj is IdClass other)
        {
            return Equals(other);
        }
        return false;
    }

    /// <summary>
    /// Checks for equality among <see cref="IdClass"/> classes
    /// </summary>
    /// <param name="other">The other <see cref="IdClass"/> to compare it to</param>
    /// <returns>True if equal</returns>
    public virtual bool Equals(IdClass other)
    {
        if (other == null) return false;
        return ID.Equals(other.ID);
    }

    /// <summary>
    /// Calculates the hash code for the <see cref="IdClass"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode() => ID.GetHashCode();

    #endregion

}

具有属性的类

这种情况与上述类似,但是比较取决于两个或多个属性,并且需要在哈希码中进行非对称组合。在下一个场景中,这一点将变得更加明显,但是我们的想法是,如果一个属性具有哈希A,而另一个属性具有哈希B,则结果应该与第一个属性具有哈希{{ }}和另一个哈希B

A

基于值的类(结构)

这几乎与上面的情况相同,除了作为值类型(public class RefClass : IEquatable<RefClass> { public string Name { get; } public int Age { get; } #region IEquatable Members /// <summary> /// Equality overrides from <see cref="System.Object"/> /// </summary> /// <param name="obj">The object to compare this with</param> /// <returns>False if object is a different type, otherwise it calls <code>Equals(RefClass)</code></returns> public override bool Equals(object obj) { if (obj is RefClass other) { return Equals(other); } return false; } /// <summary> /// Checks for equality among <see cref="RefClass"/> classes /// </summary> /// <param name="other">The other <see cref="RefClass"/> to compare it to</param> /// <returns>True if equal</returns> public virtual bool Equals(RefClass other) { if (other == null) { return false; } return Name.Equals(other.Name) && Age.Equals(other.Age); } /// <summary> /// Calculates the hash code for the <see cref="RefClass"/> /// </summary> /// <returns>The int hash value</returns> public override int GetHashCode() { unchecked { int hc = -1817952719; hc = (-1521134295) * hc + Name.GetHashCode(); hc = (-1521134295) * hc + Age.GetHashCode(); return hc; } } #endregion } 声明)还需要重新定义struct==才能调用equals。

!=

请注意,public struct ValClass : IEquatable<ValClass> { public int X { get; } public int Y { get; } #region IEquatable Members /// <summary> /// Equality overrides from <see cref="System.Object"/> /// </summary> /// <param name="obj">The object to compare this with</param> /// <returns>False if object is a different type, otherwise it calls <code>Equals(ValClass)</code></returns> public override bool Equals(object obj) { if (obj is ValClass other) { return Equals(other); } return false; } public static bool operator ==(ValClass target, ValClass other) { return target.Equals(other); } public static bool operator !=(ValClass target, ValClass other) { return !(target == other); } /// <summary> /// Checks for equality among <see cref="ValClass"/> classes /// </summary> /// <param name="other">The other <see cref="ValClass"/> to compare it to</param> /// <returns>True if equal</returns> public bool Equals(ValClass other) { return X == other.X && Y == other.Y; } /// <summary> /// Calculates the hash code for the <see cref="ValClass"/> /// </summary> /// <returns>The int hash value</returns> public override int GetHashCode() { unchecked { int hc = -1817952719; hc = (-1521134295) * hc + X.GetHashCode(); hc = (-1521134295) * hc + Y.GetHashCode(); return hc; } } #endregion } 应该是不可变的,在声明中添加struct关键字是一个好主意

readonly