C#,覆盖GetHashCode和Equals时应考虑哪些类字段/成员?

时间:2015-02-09 16:31:03

标签: c# equals gethashcode iequatable

关于这个主题有这个极好的问题和答案: 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;
        }
    }
    
  3. 如果我只使用FirstName会怎么样?

2 个答案:

答案 0 :(得分:1)

  

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

某些比较使用通用版本,有些使用非通用版本。如果你已经拥有通用版本,这是一个相当简单的实现,那么实现它是没有害处的。

  

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

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

所以你的GetHashCode可以做任何事情,从返回一个常量(punt并使用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());

答案 1 :(得分:0)

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

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