【问题标题】:C#, Which class fields/members should be considered when overriding GetHashCode and Equals?C#,在覆盖 GetHashCode 和 Equals 时应考虑哪些类字段/成员?
【发布时间】:2015-02-09 16:31:02
【问题描述】:

关于这个主题有一个很好的问题和答案: Do I HAVE to override GetHashCode and Equals in new Classes?

正如它所提到的:

只有在需要值相等语义时才需要覆盖它们。 System.Object 实现并不“坏”,它只是做一个引用检查(这是该级别的所有实现都可以做的)。

简而言之:如果您需要某种基于值的相等性(基于类属性的相等性),那么可以,覆盖掉。否则,它应该已经很好了。

假设我有一个类用户:

public class User: IEquatable<User>
{
    private readonly string _firstName;
    private readonly string _lastName;
    private readonly string _address;

    public User (string firstName, string lastName, string address)
    {       
        this._firstName = firstName;
        this._lastName = lastName;
        this._address = address;
    }

    public FirstName {get; private set;}
    public LastName {get; private set;}
    public Address {get; private set;}


    //should I need to override this?
    public override bool Equals(object right)
    {
        if (object.ReferenceEquals(right, null))
            return false;

        if (object.ReferenceEquals(this, right))
            return true;

        if (this.GetType() != right.GetType())
            return false;

        return this.Equals(right as User);
    }

    #region IEquatable<User> Members
    public bool Equals(User user)
    {
        bool isEqual = (this._firstName != null && this._firstName.Equals(user.FirstName, StringComparison.InvariantCultureIgnoreCase)) || 
                      (this._lastName != null && this._lastName.Equals(user.LastName, StringComparison.InvariantCultureIgnoreCase)) ||
                      (this._address != null && this._address.Equals(user.Address, StringComparison.InvariantCultureIgnoreCase)) ||
                      (this._firstName == null && this._lastName == null && this._address == null);
        return isEqual; 
    }
    #endregion

}

User user1 = new User("John", "Wayne", "Collins Avenue");
User user2 = new User("John", "Wayne", "Collins Avenue");

//if I don't override these methods, reference equals will be:
user1 == user2 // false

//if I override GetHashCode and Equals methods, then:
user1 == user2 //true

IList<User> usersList1 = new List<User>();
IList<User> usersList2 = new List<User>();

usersList1.Add(user1);
usersList2.Add(user2);

IList<User> finalUsersList = usersList1.Union(usersList2);

//if I don't override these methods, count will be:
finalUsersList.Count() // 2
//if I do override these methods, count will be:
finalUsersList.Count() // 1 

对吗?

  1. 需要注释的第一个 Equals 覆盖方法?
  2. 在这种情况下,我应该在 GetHashCode 覆盖中包含哪些类成员? Equals 方法中涉及的所有成员?

    public override int GetHashCode()
    {
        unchecked
        {
            // Hash -> primes
            int hash = 17;
    
            hash = hash * 23 + FirstName.GetHashCode();
            hash = hash * 23 + LastName.GetHashCode();
            hash = hash * 23 + Address.GetHashCode();
            return hash;
        }
    }
    

如果我只使用 FirstName 会发生什么?

【问题讨论】:

  • 好吧,什么时候人和你一样?
  • 如果名字、姓氏和地址相等。
  • @AlbertoMontellano 那就是你的答案。
  • 您可以定义GetHashCode() 来返回一个常量,它仍然可以工作——但效率不高。哈希用于快速确定两个对象是否可能相同,而无需运行完整的Equals 方法来查找。对于您认为“相等”的属性集,您的哈希值越独特越好。例如,如果您将对象放入字典中,则哈希决定了它们在内部存储桶中的分布方式。如果您的大多数(不相等的)对象返回唯一的哈希码,那么冲突率会很低,并且查找速度会很快。
  • @Cameron 你的回答是对的。

标签: c# equals gethashcode iequatable


【解决方案1】:

需要注释的第一个 Equals 覆盖方法?

有些比较使用通用版本,有些使用非通用版本。如果您已经拥有通用版本,那么这是一个相当简单的实现,因此实现它并没有什么坏处。

在这种情况下,我应该在 GetHashCode 覆盖中包含哪些类成员? Equals 方法中涉及的所有成员?

GetHashCode 的唯一要求是两个“相等”的对象必须返回相同的哈希码(反之则不然 - 两个相等的哈希码并不意味着相等的对象)。

所以你的GetHashCode 可以做任何事情,从返回一个常量(平底船并使用Equals 来确定相等性)到一个返回尽可能多不同哈希码的复杂函数。

为了在使用基于散列的集合时获得合理的性能,您应该设计您的GetHashCode 逻辑以最大限度地减少冲突。这通常是通过迭代地将哈希码乘以一个素数来完成的。

另外一个关键是哈希码不能改变,这意味着推导出哈希码的值不能改变。如果哈希码在对象的整个生命周期内发生变化,则无法在字典中找到该项目,因为它根据哈希值存储项目。

如果你想基于一个可以改变的值来定义“平等”,你最好在一个实现IEqualityComparer的单独的类中这样做,但需要注意的是,如果对象不应该被修改用于进行基于哈希的查找。

例如,如果我只使用FirstName 会发生什么?

与使用所有相关字段相比,您可能会遇到更多的冲突,这仅意味着系统在基于散列的集合中查找项目时需要做更多的工作。它首先找到所有具有计算哈希值的对象,然后使用Equals 与原始对象进行检查。

请注意,在您的实现中,您应该对 FirstNameLastNameAddress 进行空值检查:

    hash = hash * 23 + (FirstName == null ? 0 : FirstName.GetHashCode());
    hash = hash * 23 + (LastName  == null ? 0 : LastName.GetHashCode());
    hash = hash * 23 + (Address   == null ? 0 : Address.GetHashCode());

【讨论】:

    【解决方案2】:

    这取决于您打算如何比较用户。如果您希望相等比较仅在您比较对同一用户对象的两个引用时返回 true,则不需要您的 equals 覆盖。

    但是,如果您想根据其他逻辑比较用户,那么您应该在 equals 和 GetHashCode 实现中使用哪些字段的答案取决于您的具体上下文。在进行相等比较时,如果两个用户的名字和姓氏相同,您会认为他们相等吗?如果他们的名字和姓氏相同但地址不同怎么办?您认为定义唯一用户的字段都是您应该使用的字段。

    【讨论】:

    • 在这种情况下,它不是由Equals接口方法控制的吗?为什么需要 GetHashCode?​​span>
    • @AlbertoMontellano 查看该方法的文档,了解它存在的原因和用途。
    • @AlbertoMontellano 我会向您推荐这个SO Question,了解为什么覆盖 GetHashCode 很重要
    • 你是对的人,似乎答案是重写 GetHashCode 很重要,但它首先检查 GetHashCode,然后检查 Equals 方法。
    猜你喜欢
    • 2010-09-06
    • 2011-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多