覆盖Equals和GetHash

时间:2010-06-23 15:58:27

标签: c#

我已经读过,当您在类/对象上重写Equals时,您需要覆盖GetHashCode。

 public class Person : IEquatable<Person>
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Person(int personId, string firstName, string lastName)
        {
            PersonId = personId;
            FirstName = firstName;
            LastName = lastName;

        }

        public bool Equals(Person obj)
        {
            Person p = obj as Person;

            if (ReferenceEquals(null, p)) 
                return false;
            if (ReferenceEquals(this, p)) 
                return true;

            return Equals(p.FirstName, FirstName) && 
                   Equals(p.LastName, LastName);
        }


    }

现在给出以下内容:

 public static Dictionary<Person, Person> ObjDic= new Dictionary<Person, Person>();
 public static Dictionary<int, Person> PKDic = new Dictionary<int, Person>();

不会覆盖GetHashCode会影响上面的两个词典吗?我基本上问的是GetHashCode是如何生成的?如果我仍在寻找PKDic中的对象,我将能够根据PK找到它。如果我想覆盖GetHashCode怎么会这样做呢?

4 个答案:

答案 0 :(得分:4)

您应始终覆盖GetHashCode

Dictionary<int, Person>将在没有GetHashCode的情况下运行,但只要您调用DistinctGroupBy等LINQ方法,它就会停止工作。

请注意,您实际上还没有覆盖EqualsIEquatable.Equals方法与从virtual bool Equals(object obj)继承的Object方法不同。虽然默认IEqualityComparer<T>将使用IEquatable<T>接口,但如果类实现它,您仍应覆盖Equals,因为其他代码可能不会。{/ p>

在您的情况下,您应该覆盖EqualsGetHashCode,如下所示:

public override bool Equals(object obj) { return Equals(obj as Person); }
public override int GetHashCode() {
    return FirstName.GetHashCode() ^ LastName.GetHashCode();
}

答案 1 :(得分:4)

在您的方案中,不覆盖您的类型GetHashCode将仅影响第一个字典,因为密钥是用于散列的内容,而不是值。

在查找密钥的存在时,Dictionary<TKey,TValue>将使用哈希代码来查明是否相等的任何密钥。重要的是要注意,哈希值是一个值,可以确定是否两个相等或很可能相等。严格来说,哈希不能确定两个项是否相等。

两个相等的对象 required 返回相同的哈希码。但是,返回不同的哈希码需要两个不相等的对象 。换句话说,如果哈希码不匹配,则可以保证对象不相等。如果哈希码执行匹配,则对象可以相等。

因此,Dictionary只会在两个对象的哈希码匹配时调用Equals

关于“如何覆盖GetHashCode”,这是一个复杂的问题。实际上,散列算法应该在具有低冲突率的值集合上均匀分布代码之间取得平衡(当两个不相等的对象产生相同的代码时发生冲突)。这是一个简单的描述和完成非常困难的事情。做一个或另一个很容易,但很难平衡它们。

从实际角度来看(意味着忽视性能),你可以XOR使用名字和姓氏的所有字符(甚至使用各自的哈希码,如Joel建议的那样)作为哈希码。这将产生低程度的碰撞,但不会导致非常均匀的分布。除非您处理非常大的集合或非常频繁的查找,否则它不会成为问题。

答案 2 :(得分:3)

您的GetHashCode()和Equals()方法应如下所示:

public int GetHashCode()
{
    return (FirstName.GetHashCode()+1) ^ (LastName.GetHashCode()+2);
}


public bool Equals(Object obj)
{
    Person p = obj as Person;

    if (p == null) 
        return false;

    return this.Firstname == p.FirstName && this.LastName == p.Lastname;
}

规则是GetHashCode()必须使用确定.Equals()方法相等性的字段。

至于问题的字典部分,.GetHashCode()用于确定字典中的键。但是,这会对您问题中的每个词典产生不同的影响。

带有int键的字典(可能是你的人ID)将使用GetHashCode()作为整数,而另一个字典(ObjDic)将使用Person对象中的GetHashCode()。因此,PKDic将始终区分具有不同ID的两个人,而ObjDic可能会将两个具有不同ID但姓名相同的人视为同一记录。

答案 3 :(得分:1)

我将如何做到这一点。由于两个不同的人通常使用完全相同的名称,因此使用唯一标识符(您已经拥有)更有意义。

public class Person : IEquatable<Person>
{
  public override int GetHashCode()
  {
    return PersonId.GetHashCode();
  }

  public override bool Equals(object obj)
  {
    var that = obj as Person;
    if (that != null)
    {
      return Equals(that);
    }
    return false;
  }

  public bool Equals(Person that)
  {
    return this.PersonId == that.PersonId;
  }
}

回答您的具体问题:仅当您使用Person作为IDictionary集合中的密钥时才重要。例如,Dictionary<Person, string>SortedDictionary<Person, Foo>,但不是Dictionary<int, Person>