繁体   English   中英

C#,在重写GetHashCode和Equals时应考虑哪些类字段/成员?

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

关于此主题有一个出色的问答: 我是否必须重写新类中的GetHashCode和Equals?

正如它提到的:

仅在需要值相等语义时才需要覆盖它们。 System.Object实现不是“不好的”,它仅执行引用检查(该级别的所有实现都可以做到)。

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

假设我有一个User类:

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重写中包括哪些类成员? 所有参与平等方法的成员?

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

例如,如果我仅使用名字,会发生什么?

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

有些比较使用通用版本,有些则使用非通用版本。 由于这是一个相当琐碎的实现,如果您已经拥有通用版本,则实现它不会有任何危害。

在这种情况下,应该在GetHashCode重写中包括哪些类成员? 所有参与平等方法的成员?

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM