C#,覆盖GetHashCode和Equals时应该考虑哪个class fields/members?

C#, Which class fields/members should be considered when overriding GetHashCode and Equals?

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

正如它提到的:

you only need to override them if you need value equality semantics. The System.Object implementation isn't 'bad', it just only does a reference check (which is all an implementation at that level can do).

In short: If you need some sort of value based equality (equality based on properties of the class), then yes, override away. Otherwise, it should be more than fine already.

假设我有一个 class 用户:

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 覆盖中包含哪些 class 成员? 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 会怎样?

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

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

The first Equals override method commented is required?

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

In this case, which class members should I include in the GetHashCode override? All the members involved in the Equals method?

GetHashCode 的唯一 要求 是 "equal" 的两个对象必须 return 相同的哈希码(反之则不是true - 两个相等的哈希码并不意味着相等的对象)。

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

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

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

如果您想根据可以更改的值定义 "equality",您最好在实现 IEqualityComparer 的单独 class 中执行此操作,但需要注意的是如果要将对象用于进行基于散列的查找,则不应修改这些对象。

What happens if I only use FirstName for example?

与使用所有相关字段相比,您可能会遇到更多的冲突,这仅意味着在基于散列的集合中查找项目时,系统必须做更多的工作。它首先找到所有具有计算哈希值的对象,然后使用 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());