我理解的GetHashCode,就是一个判断对象是否相等的快速检查器。
判断相等,是一种最常见的运算之一。对于整型,浮点型这种值类型,是几就是几。而对于引用类型,为了判断两个对象是不是相等,就需要重写Object.Equals方法——由造物主,也就是你,来规定怎么样的两个对象算是相等的。
在我们把一个对象添加到一个散列表中时,会先判断这个对象是不是在其中,因为散列表是不允许出现碰撞的。在判断时,并不是直接上来就调用Equals方法,而是会先调用GetHashCode方法,计算出待添加对象的一个散列值:
对于Equals和GetHashCode的关系,MSDN给出的解释如下:
If you override the GetHashCode method, you should also override Equals, and vice versa. If your overridden Equals method returns true when two objects are tested for equality, your overridden GetHashCode method must return the same value for the two objects.
咱们可以说得再清楚些,就是:Equals方法和GetHashCode方法的重写应该同时存在。如果Equals方法返回的结果是true,那么GetHashCode方法返回的结果应该相同。如果GetHashCode方法返回的结果相同,那么Equals方法返回的结果不一定是true。
下面用人话,举例解释一遍:
家里养了好多小动物,要喂食。喂过了的小动物,就记录一下。记录使用了HashSet。
上面的小例子里,先用动物的种类来判断待喂的动物有没有被喂过,就是调用GetHashCode;用动物的名字来判断有没有喂过,就是调用Equals。
定义Student类:
class Student
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public Student(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public override bool Equals(object obj)
{
Console.WriteLine("Equals");
if (obj == null || this.GetType() != obj.GetType())
return false;
return this.FirstName == ((Student)obj).FirstName
&& this.LastName == ((Student)obj).LastName;
}
public override int GetHashCode()
{
Console.WriteLine("GetHashCode");
return this.FirstName.GetHashCode();
}
}
执行代码:
Student student1 = new Student("Jun", "Lei");
Student student2 = new Student("Kaifu", "Li");
Student student3 = new Student("Jun", "Zhu");
var dic = new Dictionaryobject>();
dic[student1] = new object();
Console.WriteLine("==================");
Console.WriteLine(dic.ContainsKey(student2));
Console.WriteLine("==================");
Console.WriteLine(dic.ContainsKey(student3));
结果为:
通过运行结果,我们可以看到:如果GetHashCode返回的结果相同的话,就没必要调用Equals了;如果GetHashCode返回的结果不同,才会再调用Equals方法,做精确对比。
在上面的demo中,细心的你应该注意到了一个小细节:但是当你只override了Object.Equals方法后,你会发现虽然编译没有error,但是会有一个warn:
‘Test.Student’ overrides Object.Equals(object o) but does not override Object.GetHashCode()
如果再override了Object.GetHashCode方法后,一切就都正常了。
那么这个warn存在意义在哪里呢。我们再把上面的例子稍微修改一下。
先注释掉Student类中重写的GetHashCode方法,然后调用以下代码:
Student student1 = new Student("Jun", "Lei");
var dic = new Dictionaryobject>();
dic[student1] = new object();
Console.WriteLine(dic.ContainsKey(new Student("Jun", "Lei")));
你会发现,返回的结果是False
。
如果我们再把Student类中的GetHashCode方法反注释掉,再次执行上面的代码,会发现,返回的结果是True
。
这两次不同的运行结果,就是GetHashCode的意义所在。对于引用对象,我们关心的是这个对象所代表的实际含义,比如,其中若干数值的值大小,所以我们判断相等,就要看这些值是不是对应相等,而和这个对象是什么时候创建出来的无关。一个二维的Point,就是由x和y两个值决定的,你就算是new出来1万个Point对象,它们逻辑上都是表示同一个点。
假设我们正确地重写了Equals方法,能判断出来这1万个点都是同一个点,但是我们没有重写GetHashCode方法,这时,如果我们把这个点对象作为散列表的Key来使用,那么就会先调用Object.GetHashCode方法,此时这1万个位置相同的点,就不再是1个点,而是1万个不同的点,因为它们是不同的对象。