49

我正在使用域模型,并且正在考虑我们必须在 .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();
}
4

7 回答 7

38

领域驱动设计区分实体值对象。这是一个很好的区别,因为它指导您如何实现 Equals。

如果实体的 ID 彼此相等,则它们是相等的。

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

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

于 2010-03-02T12:55:25.227 回答
12

这里没有一个答案真的很适合我。既然您已经说过您不能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;
}

有关这背后的一些原因,请参阅Jon Skeet 的回答。使用异或并不好,因为不同的数据集最终可能会产生相同的哈希值。这种使用素数(上面的种子值 19 和 31,或您选择的其他值)的环绕方法可以更好地分割成每个几乎没有冲突的“桶”。

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

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

于 2016-01-21T00:14:16.157 回答
6

由于哈希码相等而假设实例相等是错误的。

我猜你的 GetHashCode 实现是可以的,但我通常使用类似这样的东西:

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
于 2010-03-02T12:50:59.587 回答
4

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

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

前提是:

  • 实体需要平等
  • 如果实体对象映射相同的实体,则可以认为它们是相等的,因为它们引用相同的«实体键»
  • @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;
}

就这样!

于 2018-03-27T17:02:12.980 回答
3

哈希码可能会发生冲突,所以我认为它们不是比较平等的好方法。您应该比较使对象“相等”的基础值。请参阅@Jon Skeet 对这个问题的回答:覆盖 System.Object.GetHashCode 的最佳算法是什么?如果您的相等性包含多个属性,则可以获得更好的 GetHashCode 实现。如果它只是一个属性,你可以重用它的哈希码。

于 2010-03-02T12:51:03.557 回答
1

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

我还想链接这个问题: 比较两个复杂对象的最佳方法 它是关于具有嵌套的类结构。在评论中可以找到带有枚举的嵌套类结构的情况(例如使用 List )。

于 2020-11-23T13:29:56.403 回答
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

}

具有属性的类

这种情况与上面类似,但是比较依赖于两个或多个属性,并且需要在哈希码中进行非对称组合。这将在下一个场景中变得更加明显,但想法是如果一个属性具有 hashA而另一个属性 hash B,结果应该不同于第一个属性具有 hashB和另一个 hash的情况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在声明中添加关键字

public readonly struct ValClass : IEquatable<ValClass>
{
} 
于 2020-11-23T14:20:32.450 回答