C#正确实现Equals方法和如何实现GetHashCode方法

6
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Crystal_Message
{
    class Person
    {
        private string firstName ="";
        private string lastName= "";
        private string phone="";


        public Person(string firstName, string lastName, string phone)
        {
            this.FirstName = firstName;
            this.LastName = lastName;
            this.PhoneNumber = phone;
        }

        public string FirstName
        {
            get { return firstName; }

            private set
            {
                if (string.IsNullOrWhiteSpace(value)){

                    throw new ArgumentNullException("Must Include First Name");
                }

                this.firstName = value;
            }

        }

        public string LastName
        {
            get { return lastName; }

            private set
            {
                if (string.IsNullOrWhiteSpace(value)){

                    throw new ArgumentNullException("Must Include Last Name");
                }

                this.lastName = value;
            }

        }

        public string PhoneNumber
        {
            get { return phone; }

            private set
            {
                if (string.IsNullOrWhiteSpace(value)){

                    throw new ArgumentNullException("Must Include Phone Number");
                }

                this.phone = value;
            }

        }


        public override string ToString()
        {
            return "First Name: " + this.FirstName + " " + " Last Name: " + this.LastName + " " + " Phone Number: " + this.PhoneNumber;
        }

        public override bool Equals(object obj)
        {
            if(obj == null)
            {
                return false;
            }

            Person testEquals = obj as Person;

            if((System.Object)testEquals == null)
            {
                return false;
            }

            return (this.firstName == testEquals.firstName) && (this.lastName == testEquals.lastName) && (this.phone == testEquals.phone);   

        }

        /*
        public override int GetHashCode()
        {
           return 
        }
        */ 
    }
}

我一直在遵循MSDN的指导方针。两个问题:

  1. 我是否正确实现了equals方法?
  2. 有谁能为我的类正确地实现GetHashCode?MSDN使用x ^ y,但我不能这样做。

2
当然可以使用异或(XOR),将其用于成员的哈希码上。你没有必要这样做,因为只需返回phone.GetHashCode()即可。这很有效,因为每个人都有一个独特的电话号码。 - Hans Passant
@HansPassant 并不是每个人都有唯一的电话号码。有些人可能选择不输入他们的电话号码,即使他们有一个,而其他人可能共享一个电话号码(公司或家庭电话)。 - user743382
1
唉,GetHashCode() 不需要是唯一的,只需要足够好。 - Hans Passant
@hvd - 很好的发现,我该如何正确实现GetHashCode? - user4981830
@hvd- 我的 equals 方法写得对吗? - user4981830
显示剩余6条评论
3个回答

6

为了避免任何问题,GetHashCode 应该使用与 Equals 相同的所有成员,反之亦然。

所以在你的情况下:

public override int GetHashCode()
{
    return firstName.GetHashCode() ^ lastName.GetHashCode() ^ phone.GetHashCode();
}

我认为这是一个不错的方法。即使两个人有相同的数字,他们的名字和姓氏也不同,以生成唯一的HashCode,对吗? - user4981830
你觉得,以防万一,我应该添加一个私有的int ID,为每个人分配一个唯一的ID号码吗?以防同一户人家可能会有相同的名字(名和姓),例如,儿子可能会以他父亲的名字命名。然而,他们两个必须有不同的ID。 - user4981830
是的,为这种类也拥有一个ID是一个好习惯。 - Filip

1

与 Filip 的答案中提到的简单异或哈希码的方法相比,更常见的方法是使用更复杂的公式来组合它们。例如,将各个字段的哈希码乘以不同的数字,如下所示:

public override int GetHashCode()
{
    unchecked
    {
        return (firstName.GetHashCode() * 33 ^ lastName.GetHashCode()) * 33 ^ phone.GetHashCode();
    }
}

(注意unchecked关键字:这里预期发生整数溢出,并且静默地环绕是预期的行为。)

对于您处理的具体类型,这可能不会有任何区别,但总体而言,这样做更好。考虑一个仅包含两个整数值的简单类型。还要考虑到intGetHashCode()实现仅返回其自身值。如果使用简单的异或来组合这些值,您将在常规代码中产生大量哈希碰撞:最简单的示例是每对两个相同的值将产生零的相同哈希码。

这里的计算实际上是由Tuple<T1,T2,T3>执行的计算。我没有按照Microsoft的方式编写它,但实际的计算和数字应该是相同的。


谢谢您。您建议我也为我的其他类做这个吗?我有一个员工类和消息类。 - user4981830
@Nexusfactor 我认为只要这样做可以更容易地验证你的 GetHashCode() 实现是否正确,那么就没有不这样做的理由。但是如果你认为这会增加额外的复杂性,并且你自己的哈希码实现已经足够好了,那么我完全可以理解坚持使用你自己的哈希码实现。 - user743382
非常感谢您的时间和解释。非常感激。只是出于好奇,您是从教程/书籍还是学校学习的呢?我很想了解更多,以便更好地理解它。 - user4981830
@Nexusfactor 我强烈推荐Eric Lippert的博客。他写了一篇关于哈希码的文章,实际上明确指出了我在这里描述的同样的问题。(请搜索“特别是要注意“xor”。”) - user743382

1

请记住这两种方法的目的:equals方法定义了在哪些情况下应该将两个类实例视为相等。因此,如果在您的情况下,仅当名字、姓氏和电话号码相同时才正确。而哈希方法则用于对实例进行排序或分发,例如在哈希映射中。它应该足够快且好,以避免不必要的聚集。因此,在哈希函数中经常看到值乘以一个质数。您必须确保相等的对象具有相同的哈希码,但反之则不然。因此,不同的对象可能具有相同的哈希码。


“因此,不同的对象可能具有相同的哈希码” - 在这种情况下,当使用HashSet时,您可能会遇到问题。因为它将调用GetHashCode并假定该对象已经存在于集合中,并且不会添加它。但实际上,这些对象并不重复... - Filip
请参阅 https://msdn.microsoft.com/library/system.object.gethashcode%28v=vs.110%29.aspx。 - Christoph Grimmer
链接在另一种语言中。 - user4981830
抱歉,我认为切换到原始模式会解决这个问题。显然,微软不希望在URL中表示这个选项。我已经修复了它。 - Christoph Grimmer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接