首页 文章

为什么GetHashCode应该实现与Equals相同的逻辑?

提问于
浏览
0

this MSDN页面中它说:

警告:如果重写GetHashCode方法,则还应该重写Equals,反之亦然 . 如果在对两个对象进行相等性测试时,重写的Equals方法返回true,则重写的GetHashCode方法必须为这两个对象返回相同的值 .

我也看到了许多类似的建议,我可以理解,当重写Equals方法时,我也想要覆盖GetHashCode . 据我所知,GetHashCode与哈希表查找一起使用,这与等式检查不同 .

这是一个帮助解释我想问的例子:

public class Temperature /* Immutable */
{
    public Temperature(double value, TemperatureUnit unit) { ... }

    private double Value { get; set; }
    private TemperatureUnit Unit { get; set; }

    private double GetValue(TemperatureUnit unit)
    {
        /* return value converted into the specified unit */
    }

    ...

    public override bool Equals(object obj)
    {
        Temperature other = obj as Temperature;
        if (other == null) { return false; }
        return (Value == other.GetValue(Unit));
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode() + Unit.GetHashCode();
    }
}

在这个例子中,两个温度对象被认为是相等的,即使它们没有在内部存储相同的东西(例如295.15 K == 22摄氏度) . 目前,GetHashCode方法将为每个方法返回不同的值 . 这两个温度对象是相同的,但它们也不相同,所以它们有不同的哈希码是不正确的?

3 回答

  • 2

    当在哈希表中存储值时,例如 Dictionary<> ,框架将首先调用 GetHashCode() 并检查哈希表中是否已存在该哈希码的存储桶 . 如果有,它将调用 .Equals() 以查看新值是否确实等于现有值 . 如果不是(意味着两个对象不同,但导致相同的哈希码),则会产生所谓的冲突 . 在这种情况下,该桶中的项目被存储为链表,并且检索特定值变为O(n) .

    如果您实现了 GetHashCode() 但没有实现 Equals() ,那么框架将使用引用相等来检查是否存在导致每个实例都发生冲突的相等性 .

    如果您实现了 Equals() 但没有实现 GetHashCode() ,则可能会遇到这样的情况:您有两个相同的对象,但导致不同的哈希代码意味着它们在哈希表中维护它们自己的单独值 . 这可能会让使用您 class 的人感到困惑 .

    至于哪些对象被认为是平等的,那取决于你 . 如果我根据温度创建哈希表,我是否应该使用其摄氏度或华氏度值来引用相同的项目?如果是这样,他们需要产生相同的哈希值, Equals() 需要返回true .

    Update:

    让我们退后一步,首先看一下哈希码的用途 . 在此上下文中,哈希码用作识别两个对象是否最可能相等的快速方法 . 如果我们有两个具有不同哈希码的对象,我们知道它们不相等 . 如果我们有两个具有相同哈希码的对象,我们知道它们很可能是相等的 . 我之所以说很可能是因为int只能用来表示几十亿个可能的值,而字符串当然可以包含Charles Dickens的全部作品,或者任意数量的可能值 . .NET框架中的许多内容都基于这些事实,使用代码的开发人员会认为事情的工作方式与框架的其余部分一致 .

    如果你有两个具有不同哈希码的实例,但实现 Equals() 返回true,那么你就违反了这个约定 . 比较两个对象的开发人员可能会使用其中一个对象来引用哈希表中的键,并期望获得现有值 . 如果哈希代码突然不同,则此代码可能会导致运行时异常 . 或者可能返回对完全不同的对象的引用 .

    295.15k和22C在你的程序范围内是否相等是你的选择(在我看来,它们不是) . 但是,无论你决定什么,相同的对象必须返回相同的代码 .

  • 5

    警告:如果重写GetHashCode方法,则还应该重写Equals,反之亦然 . 如果在对两个对象进行相等性测试时,重写的Equals方法返回true,则重写的GetHashCode方法必须为这两个对象返回相同的值 .

    这是.NET库中的约定 . 它不是在编译时,甚至在运行时强制执行,但.NET库中的代码(可能还有任何其他外部库)都希望此语句始终为true:

    如果两个对象从Equals返回true,则它们将返回相同的哈希码

    和:

    如果两个对象返回不同的哈希码,则它们不相等

    如果你不遵循这一点惯例,然后你的代码将破裂 . 更糟糕的是,它可能会破坏真正难以追踪的方式(例如将两个相同的对象放入字典中,或者从字典中获取与您预期的对象不同的对象) .

    所以,遵循惯例,否则你会给自己带来很多悲伤 .

    在您的特定类中,您需要决定, Equals 在单位不同时返回false,或者 GetHashCode 返回相同的哈希码而不管单位 . 你不可能两种方式 .

    所以你要么这样做:

    public override bool Equals(object obj)
    {
        Temperature other = obj as Temperature;
        if (other == null) { return false; }
        return (Value == other.Value && Unit == other.Unit);
    }
    

    或者你这样做:

    public override int GetHashCode()
    {
        // note that the value returned from ConvertToSomeBaseUnit
        // should probably be cached as a private member 
        // especially if your class is supposed to immutable
        return Value.ConvertToSomeBaseUnit().GetHashCode();
    }
    

    请注意,没有什么能阻止您实施:

    public bool TemperaturesAreEqual(Temperature other)
    {
        if (other == null) { return false; }
        return (Value == other.GetValue(Unit));
    }
    

    当你想知道两个温度是否代表相同的物理温度而不管单位时使用它 .

  • 1

    两个相等的对象应该返回相同的HashCode(两个不同的对象也可以返回相同的哈希码,但这是一个冲突) .

    在您的情况下,您的equals和hashcode实现都不是很好 . 问题在于对象的“实际值”取决于参数:没有单个属性定义对象的值 . 您只存储初始单元以进行相等比较 .

    那么,为什么要_2961139_是 ValueTemperature

    我实现它像:

    public class Temperature
    {
        public Temperature(double value, TemperatureUnit unit) { 
           Value = ConvertValue(value, unit, TemperatureUnit.Celsius);
        }
    
        private double Value { get; set; }
    
        private double ConvertValue(double value, TemperatureUnit originalUnit,  TemperatureUnit targetUnit)
        {
           /* return value from originalUnit converted to targetUnit */
        }
        private double GetValue(TemperatureUnit unit)
        {
           return ConvertValue(value, TemperatureUnit.Celsius, unit);
        }    
        public override bool Equals(object obj)
        {
            Temperature other = obj as Temperature;
            if (other == null) { return false; }
            return (Value == other.Value);
        }
    
        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
    }
    

    这样,您的内部 Value 定义了两个对象是否相同,并且始终以相同的单位表示 .

    你真的不关心对象具有什么:它没有意义,因为为了获得值,你总是会传递一个值 . 将它传递给初始转换才有意义 .

相关问题