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

时间:2016-01-15 19:08:11

标签: c# equality gethashcode

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 个答案:

答案 0 :(得分:5)

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

如果您实施了GetHashCode()实施Equals(),那么框架将使用引用相等来检查是否会产生相等性在每个实例中创建一个碰撞。

如果您实现了Equals()实现GetHashCode(),则可能会遇到两个对象相同但导致哈希代码含义不同的情况他们在哈希表中维护自己独立的值。这可能会使使用您班级的任何人感到困惑。

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

<强>更新

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

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

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

答案 1 :(得分:2)

  

警告

     

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

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

  

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

  

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

如果您不遵守该约定,那么您的代码将会中断。更糟糕的是,它可能会破坏真正难以追踪的方式(例如将两个相同的对象放入字典中,或者从字典中获取与您预期的对象不同的对象)。

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

在您的特定课程中,您需要确定,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));
}

当你想知道两个温度是否代表相同的物理温度时使用它,无论单位如何。

答案 2 :(得分:1)

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

在您的情况下,您的equals和hashcode实现都不是很好。问题是&#34;真正的价值&#34;对象的取决于参数:没有定义对象值的单个属性。您只存储初始单位以进行相等比较。

那么,为什么不确定内容定义Value Temperature的内容?

我实施它就像:

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:它没有任何意义,因为为了获得价值,你总是会传递一个值。将它传递给初始转换才有意义。