繁体   English   中英

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

[英]Why should GetHashCode implement the same logic as Equals?

此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方法将为每个方法返回不同的值。 这两个温度对象相等但它们也不相同,因此具有不同的哈希码是否正确?

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

如果您实现GetHashCode() ,但没有实现Equals() ,该框架将诉诸使用参考平等检查,这将导致每个实例创建碰撞平等。

如果您实现Equals() ,但没有实现GetHashCode() ,你可以在这里你有这样的人相等的两个对象,但造成了不同的散列码意味着他们会保持在你的哈希表自己独立的价值观碰到的情况。 这可能会使使用您的课程的任何人感到困惑。

至于什么对象被认为是相等的,则取决于您。 如果我根据温度创建一个哈希表,是否可以使用摄氏温度或华氏温度值引用同一项目? 如果是这样,它们需要得到相同的哈希值, 并且 Equals()需要返回true。

更新:

让我们退后一步,首先了解一下哈希码的用途。 在这种情况下,将哈希码用作识别两个对象是否最有可能相等的快速方法。 如果我们有两个具有不同哈希码的对象,那么我们就知道它们相等。 如果我们有两个具有相同哈希码的对象,我们知道它们很可能相等。 我说这很可能是因为int只能用来表示数十亿个可能的值,而字符串当然可以包含Charles Dickens的完整著作或任何数量的可能值。 .NET框架中的大部分内容都是基于这些事实,因此使用您的代码的开发人员将假定事情的工作方式与框架的其余部分保持一致。

如果要有两个具有不同哈希码的实例,但是要实现一个返回true的Equals()实现,则您将违反此约定。 比较两个对象的开发人员随后可能会使用这些对象之一来引用哈希表中的键,并期望获得现有值。 如果突然之间哈希码不同,则该代码可能会导致运行时异常。 或者返回对完全不同的对象的引用。

在您的程序域内295.15k和22C是否相等是您的选择(我认为不是)。 但是,无论您决定什么,相等的对象都必须返回相同的具有代码的对象。

警告

如果覆盖GetHashCode方法,则还应该覆盖Equals,反之亦然。 如果在测试两个对象是否相等时重写的Equals方法返回true,则重写的GetHashCode方法必须为两个对象返回相同的值。

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

如果两个对象从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));
}

当您想知道两个温度是否代表相同的物理温度时,可以使用该单位。

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

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

那么,为什么不对Temperature Value有什么内部定义呢?

我会像这样实现它:

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将定义两个对象是否相同,并且始终以同一单位表示。

您实际上并不在乎对象具有什么Unit :这没有任何意义,因为要取回值,您将始终传递值。 仅将其传递给初始转换才有意义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM