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

发布于 2024-08-23 15:33:36 字数 557 浏览 4 评论 0原文

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

I'm working with a domain model and was thinking about the various ways that we have to implement these two methods in .NET. What is your preferred strategy?

This is my current implementation:

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();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

断桥再见 2024-08-30 15:33:37

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

实体如果 ID 相同,则它们相等。

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

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

Domain-Driven Design makes the distinction between Entities and Value Objects. This is a good distinction to observe since it guides how you implement Equals.

Entities are equal if their IDs equal each other.

Value Objects are equal if all their (important) constituent elements are equal to each other.

In any case, the implementation of GetHashCode should base itself on the same values that are used to determine equality. In other words, for Entities, the hash code should be calculated directly from the ID, whereas for Value Objects it should be calculated from all the constituent values.

飘落散花 2024-08-30 15:33:37

这里的答案都没有真正适合我。既然您已经说过不能使用 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,或您选择的其他值)的环绕方法可以更好地分割成每个碰撞很少的“桶”。

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

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

None of the answers here really hit the spot for me. Since you already said that you can't use Id for equality, and you need to use a bundle of properties, here's a better way to do that. Note: I do not consider this overall to be the best way to implement Equals and GetHashCode. This is a better version of the OP's code.

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;
}

See this answer by Jon Skeet for some of the reasoning behind this. Using xor is not good because various sets of data can end up resulting in the same hash. This wrap-around method with primes (the seed values of 19 and 31 above, or other values that you choose) does a better job of segmenting into "buckets" that have few collisions each.

If any of your values can be null, I encourage you to think carefully about how they should compare. You could use short circuit null evaluation and the null coalescing operator perhaps. But make sure that if nulls should compare as equal that you assign different hash codes to the different nullable properties when they are null.

Also, I'm not convinced that your Equals implementation makes any sense. When two objects are compared for equality, first their GetHashCode values are compared. Only if those are different is the Equals method run (so that if two objects that hash to the same value are different, this will be detected). Since your GetHashCode implementation doesn't refer to the base, it may make no sense for your Equals method to do so. Specifically, you will have a serious bug waiting to break things if Equals can return true for two objects whose hash codes are different.

扬花落满肩 2024-08-30 15:33:37

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

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

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

Assuming that the instances are equal because the hash codes are equal is wrong.

I guess your implementation of GetHashCode is OK, but I usually use things similar to this:

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
迷路的信 2024-08-30 15:33:37

我偶然发现了这个老问题,恕我直言,我没有找到任何清晰简单的答案来说明@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;
}

仅此而已!

I stumbled upon this old question and, IMHO, I didn't find any answer clear and simple stated the original question formulated by @tucaz.

I can agree with many considerations shared above (or below :D) but the «Question point» was missed (I think).

Provided that:

  • Equality is required for entities
  • Entity-Objects can be considered equals if they map the same entity, id est they refer to the same «Entity Key»
  • The example shown by @tucaz just mention an «Id» (see the over-implemented GetHashCode())… not to mention the buggy Equals(…)

I can guess that one straightforward implementation could be:

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;
}

That's all!

她如夕阳 2024-08-30 15:33:37

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

Hashcodes can collide so I don't think they are a good way to compare equality. You should compare the underlying values that make the objects "equal" instead. See @Jon Skeet's answer to this question: What is the best algorithm for an overridden System.Object.GetHashCode? for a better GetHashCode implementation if your equality encompasses several properties. If it's just a single property, you can just reuse it's hashcode.

给妤﹃绝世温柔 2024-08-30 15:33:37

除了答案(我不允许写评论)之外,我想指出 Visual Studio 可以自动生成 Equals 和 GetHashCode。
请参阅此答案: Visual Studio中有没有办法自动生成equals和hashcode方法
我确实在寻找自定义实现,但在这里没有找到。

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

In addition to the answers (I am not allowed to write comments) I would like to point out that Visual Studio can autogenerate Equals and GetHashCode.
See this answer: Is there a way to automatically generate equals and hashcode method in Visual Studio
I was really looking for that custom implementation and didnt find it here.

I woulld also like to link this question:
Best way to compare two complex objects
It is about having a nested class structure. Down in the comments one can find the case for nested class structures with Enumerations (for example with List ).

葬心 2024-08-30 15:33:37

我想根据上面的答案和我自己的经验来看看一些具体的场景。

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

此外,许多内置机制都会寻找 IEquatable 的实现,因此最好声明一个 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 关键字

public readonly struct ValClass : IEquatable<ValClass>
{
} 

I want to look at some specific scenarios based on the answers above, and my own experiences.

A rule of thumb is, two instances with different hash codes should always be not equal, but if the have the same hash code they might or might not be equals. GetHashCode() is used to differentiate between instances quickly, and Equals() is used to verify equality (whatever that means to you).

Also a lot of built-in mechanisms look for an implementation of IEquatable<T> so it is a good idea to declare an override of Equals(MyClass) that actually does the checking.

Class with unique ID

Consider a class with a unique ID. Then the equals operation would just check the id. The same with the hash, which solely relies on the 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

}

Class with properties

This case is similar to the above, but the comparisons depend on two or more properties, and the need to be combined asymmetrically in the hash code. This will become more evident in the next scenario, but the idea is if one property has hash A and the other property hash B, the result should difference from the case where first property has hash B and the other 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

}

Value based class (structure)

This is almost identical to the case above, except being a value type (struct declaration) requires also re-definition of == and != to call 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

}

Note that struct should be immutable, and it is a good idea to add the readonly keyword in the declaration

public readonly struct ValClass : IEquatable<ValClass>
{
} 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文