简体   繁体   English

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

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

In this MSDN page it says: 此MSDN页面中,它说:

Warning: 警告:

If you override the GetHashCode method, you should also override Equals, and vice versa. 如果覆盖GetHashCode方法,则还应该覆盖Equals,反之亦然。 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方法返回true,则覆盖的GetHashCode方法必须为两个对象返回相同的值

I have also seen many similar recommendations and I can understand that when overriding the Equals method I would also want to override the GetHashCode. 我也看到了许多类似的建议,并且我可以理解,当覆盖Equals方法时,我也想覆盖GetHashCode。 As far as I can work out though, the GetHashCode is used with hash table look-ups, which is not the same as equality checking. 据我所知,GetHashCode与哈希表查找一起使用,这与相等性检查不同。

Here is an example to help explain what I want to ask: 这是一个示例,可以帮助解释我要问的问题:

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();
    }
}

In this example, two Temperature objects are considered equal, even if they are not storing the same things internally (eg 295.15 K == 22 Celsius). 在此示例中,即使两个温度对象在内部未存储相同的内容,也认为它们是相等的(例如295.15 K == 22摄氏度)。 At the moment the GetHashCode method will return different values for each. 目前,GetHashCode方法将为每个方法返回不同的值。 These two temperatures objects are equal but they are also not the same, so is it not correct that they have different hash codes? 这两个温度对象相等但它们也不相同,因此具有不同的哈希码是否正确?

When storing a value in a hash table, such as Dictionary<> , the framework will first call GetHashCode() and check if there's already a bucket in the hash table for that hash code. 当在哈希表中存储值(例如Dictionary<> ,框架将首先调用GetHashCode()并检查哈希表中是否已经有该哈希码的存储桶 If there is, it will call .Equals() to see if the new value is indeed equal to the existing value. 如果存在,它将调用.Equals()来查看新值是否确实等于现有值。 If not (meaning the two objects are different, but result in the same hash code), you have what's known as a collision. 如果不是(这意味着两个对象不同,但是导致相同的哈希码),则发生冲突。 In this case, the items in this bucket are stored as a linked list and retrieving a certain value becomes O(n). 在这种情况下,此存储桶中的项目将存储为链接列表,并且检索某个值将变为O(n)。

If you implemented GetHashCode() but did not implement Equals() , the framework would resort to using reference equality to check for equality which would result in every instance creating a collision. 如果您实现GetHashCode() ,但没有实现Equals() ,该框架将诉诸使用参考平等检查,这将导致每个实例创建碰撞平等。

If you implemented Equals() but did not implement GetHashCode() , you might run into a situation where you had two objects that were equal, but resulted in different hash codes meaning they'd maintain their own separate values in your hash table. 如果您实现Equals() ,但没有实现GetHashCode() ,你可以在这里你有这样的人相等的两个对象,但造成了不同的散列码意味着他们会保持在你的哈希表自己独立的价值观碰到的情况。 This would potentially confuse anyone using your class. 这可能会使使用您的课程的任何人感到困惑。

As far as what objects are considered equal, that's up to you. 至于什么对象被认为是相等的,则取决于您。 If I create a hash table based on temperature, should I be able to refer to the same item using either its Celsius or Fahrenheit value? 如果我根据温度创建一个哈希表,是否可以使用摄氏温度或华氏温度值引用同一项目? If so, they need to result in the same hash value and Equals() needs to return true. 如果是这样,它们需要得到相同的哈希值, 并且 Equals()需要返回true。

Update: 更新:

Let's step back and take a look at the purpose of a hash code in the first place. 让我们退后一步,首先了解一下哈希码的用途。 Within this context, a hash code is used as a quick way to identify if two objects are most likely equal . 在这种情况下,将哈希码用作识别两个对象是否最有可能相等的快速方法。 If we have two objects that have different hash codes, we know for a fact they are not equal. 如果我们有两个具有不同哈希码的对象,那么我们就知道它们相等。 If we have two objects that have the same hash code, we know they are most likely equal. 如果我们有两个具有相同哈希码的对象,我们知道它们很可能相等。 I say most likely because an int can only be used to represent a few billion possible values, and strings can of course contain the complete works of Charles Dickens, or any number of possible values. 我说这很可能是因为int只能用来表示数十亿个可能的值,而字符串当然可以包含Charles Dickens的完整著作或任何数量的可能值。 Much in the .NET framework is based on these truths, and developers that use your code will assume things work in a way that is consistent with the rest of the framework. .NET框架中的大部分内容都是基于这些事实,因此使用您的代码的开发人员将假定事情的工作方式与框架的其余部分保持一致。

If you were to have two instances that have different hash codes, but have an implementation of Equals() that returns true, you're breaking this convention. 如果要有两个具有不同哈希码的实例,但是要实现一个返回true的Equals()实现,则您将违反此约定。 A developer that compares two objects might then use one of of those objects to refer to a key in a hash table and expect to get an existing value out. 比较两个对象的开发人员随后可能会使用这些对象之一来引用哈希表中的键,并期望获得现有值。 If all of a sudden the hash code is different, this code might result in a runtime exception instead. 如果突然之间哈希码不同,则该代码可能会导致运行时异常。 Or perhaps return a reference to a completely different object. 或者返回对完全不同的对象的引用。

Whether 295.15k and 22C are equal within the domain of your program is your choice (In my opinion, they are not). 在您的程序域内295.15k和22C是否相等是您的选择(我认为不是)。 However, whatever you decide, objects that are equal must return the same has code. 但是,无论您决定什么,相等的对象都必须返回相同的具有代码的对象。

Warning : 警告

If you override the GetHashCode method, you should also override Equals, and vice versa. 如果覆盖GetHashCode方法,则还应该覆盖Equals,反之亦然。 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方法返回true,则重写的GetHashCode方法必须为两个对象返回相同的值。

This is a convention in the .NET libraries. 这是.NET库中的约定 It's not enforced at compile time, or even at run-time, but code in the .NET library (and likely any other external library) expects this statement to always be true: 它不是在编译时甚至在运行时都强制执行的,但是.NET库(以及可能的任何其他外部库)中的代码希望此语句始终为真:

If two object return true from Equals they will return the same hash code 如果两个对象从Equals返回true ,则它们将返回相同的哈希码

And: 和:

If two objects return different hash codes they are NOT equal 如果两个对象返回不同的哈希码,则它们相等

If you don't follow that convention, then your code will break. 如果您不遵循该约定,那么您的代码将被破坏。 And worse it will probably break in ways that are really hard to trace (like putting two identical objects in a dictionary, or getting a different object from a dictionary than the one you expected). 更糟糕的是,它可能会以难以追踪的方式中断(例如,将两个相同的对象放入字典中,或者从字典中获得与您期望的对象不同的对象)。

So, follow the convention , or you will cause yourself a lot of grief. 因此,请遵循约定 ,否则您将引起很多悲伤。

In you particular class, you need to decide, either Equals returns false when the units are different, or GetHashCode returns the same hash code regardless of unit. 在您的特定类中,您需要确定:当单位不同时, Equals返回false,或者不管单位如何, GetHashCode返回相同的哈希码 You can't have it both ways. 您不能同时拥有这两种方式。

So you either do this: 因此,您可以这样做:

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

Or you do this: 或者您这样做:

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();
}

Note that nothing is stopping you from also implementing: 请注意,没有什么可以阻止您同时实施:

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

And using that when you want to know if two temperatures represent the same physical temperature regardless of units. 当您想知道两个温度是否代表相同的物理温度时,可以使用该单位。

Two objects that are equal should return the same HashCode (two objects that are different could return the same hashcode too, but that's a collision). 两个相等的对象应返回相同的HashCode(两个不同的对象也可以返回相同的哈希码,但这是冲突)。

In your case, neither your equals nor your hashcode implementations are a good one. 在您的情况下,您的equals和hashcode实现都不是一个好方法。 Problem being that the "real value" of the object is dependant on a parameter: there's no single property that defines the value of the object. 问题在于对象的“实际值”取决于参数:没有单个属性定义对象的值。 You only store the initial unit to do equality compare. 您仅存储初始单位以进行相等比较。

So, why don't you settle on an internal definition of what's the Value of your Temperature ? 那么,为什么不对Temperature Value有什么内部定义呢?

I'd implement it like: 我会像这样实现它:

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();
    }
}

That way, your internal Value is what defines if two objects are the same, and is always expressed in the same unit. 这样,您的内部Value将定义两个对象是否相同,并且始终以同一单位表示。

You don't really care what Unit the object has: it makes no sense, since for getting the value back, you'll always pass a value. 您实际上并不在乎对象具有什么Unit :这没有任何意义,因为要取回值,您将始终传递值。 It only makes sense to pass it for the initial conversion. 仅将其传递给初始转换才有意义。

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

相关问题 为什么我不应该使用反射来实现 Equals 和 GetHashCode? - Why should I not implement Equals and GetHashCode using reflection? 如何使用覆盖的逻辑Equals()实现GetHashCode()的覆盖 - how to implement override of GetHashCode() with logic of overriden Equals() 是否有一个示例为什么应该在NHibernate中覆盖Equals / GetHashCode? - Is there a sample why Equals/GetHashCode should be overwritten in NHibernate? 所有c#类应该实现Equals和GetHashCode吗? - should all c# classes implement Equals and GetHashCode? 为什么没有 Equals() 的 GetHashCode() 没有警告 - Why no warning for GetHashCode() without Equals() 如果我的类实现了IEqualityComparer,我应该实现非通用GetHashCode和Equals吗? <T> ? - Should I implement non-generic GetHashCode and Equals if my class implements IEqualityComparer<T>? 在子类中正确实现Equals和GetHashCode - Properly Implement Equals and GetHashCode in Sub Class NHibernate设置:我应该重写Equals和GetHashCode吗? - NHibernate set : Should I override Equals and GetHashCode? 如何为HashSet覆盖Equals和GetHashCode? - How should I override Equals and GetHashCode for HashSet? 在结构中,通过Equals实现operator ==是否有效,但不能覆盖Equals和GetHashCode? - In a struct, is it valid to implement operator== via Equals, but not override Equals and GetHashCode?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM