GetHashCode 要你到底何用

我理解的GetHashCode,就是一个判断对象是否相等的快速检查器。


GetHashCode是什么

判断相等,是一种最常见的运算之一。对于整型,浮点型这种值类型,是几就是几。而对于引用类型,为了判断两个对象是不是相等,就需要重写Object.Equals方法——由造物主,也就是你,来规定怎么样的两个对象算是相等的。

在我们把一个对象添加到一个散列表中时,会先判断这个对象是不是在其中,因为散列表是不允许出现碰撞的。在判断时,并不是直接上来就调用Equals方法,而是会先调用GetHashCode方法,计算出待添加对象的一个散列值:

  1. 如果这个散列值不命中,那就说明这个对象一定不在散列中,可以添加;
  2. 如果这个散列值命中了,那也不能说明这个对象一定在散列中,因为可能散列值冲突了,所以,要继续调用Equals方法,来看看这个散列值命中的对象,到底在不在散列表里。

对于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。

  1. 先喂了一只猫Kathie——记录一下,哦,猫Kathie喂过了
  2. 然后来了一只狗Doggie——想了下,恩,刚刚喂过的是猫,所以Doggie肯定还没有喂呢,于是喂了Doggie,再记录一下
  3. 然后来了一只鹦鹉Talkie——想了下,刚刚只喂过猫和狗,没有喂过鹦鹉,所以Talkie肯定没有喂过,于是喂了Talkie,再记录下
  4. 然后来了只猫Meowie——想了下,刚刚喂过猫了,我得再好好想想,哦,刚刚喂的是Kathie,Meowie还没喂,所以喂了Meowie,记录一下
  5. 然后来了只猫Kathie——想了下,刚刚喂过猫了,我得再好好想想(这主人脑子好机械化…),这不是Kathie吗,第一个喂的就是你!又想来多吃!不喂!
  6. ……

上面的小例子里,先用动物的种类来判断待喂的动物有没有被喂过,就是调用GetHashCode;用动物的名字来判断有没有喂过,就是调用Equals。


Demo

定义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 Dictionary<Student, object>();
dic[student1] = new object();
Console.WriteLine("==================");
Console.WriteLine(dic.ContainsKey(student2));
Console.WriteLine("==================");
Console.WriteLine(dic.ContainsKey(student3));

结果为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cb346U73-1659002495984)(https://img-blog.csdn.net/20160726172219917)]

通过运行结果,我们可以看到:如果GetHashCode返回的结果相同的话,那么就需要调用Equals做进一步的精确对比;如果GetHashCode返回的结果都不相同,说明两个对象一定不相同,没必要再调用Equals方法做精确对比了。


GetHashCode的必要性

在上面的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 Dictionary<Student, object>();
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万个不同的点,因为它们是不同的对象。


总结

  1. GetHashCode是判断对象是否相等的快速检查,所以GetHashCode不需要太复杂,因为即使GetHashCode判断相同了,后面还有Equals保证精确对比。(Note:但是尽量保证GetHashCode就能判断出来,太多的hash冲突,也会增加不必要的开销。这就要看GetHashCode重写得有没有水平了)
  2. 理解重写GetHashCode的必要性。
  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值