Вопрос: Почему важно переопределить GetHashCode, когда метод Equals переопределен?


Учитывая следующий класс

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

Я переопределил Equalsметод, потому что Fooпредставляют строку для Foo. Какой предпочтительный метод для переопределения GetHashCode?

Почему важно переопределить GetHashCode?


1142


источник


Ответы:


Да, важно, чтобы ваш элемент использовался как ключ в словаре, или HashSet<T>, и т. д. - поскольку это используется (при отсутствии пользовательского IEqualityComparer<T>), чтобы группировать элементы в ведра. Если хэш-код для двух элементов не совпадает, они могут никогда считаются равными ( Equalsпросто никогда не будет называться).

GetHashCode()метод должен отражать Equalsлогика; Правила:

  • если две вещи равны ( Equals(...) == true) Затем они должен вернуть то же значение для GetHashCode()
  • если GetHashCode()равна, это не необходимо, чтобы они были одинаковыми; это столкновение, и Equalsбудет вызван, чтобы убедиться, что это реальное равенство или нет.

В этом случае это выглядит как " return FooId;"является подходящим GetHashCode()реализация. Если вы тестируете несколько свойств, обычно их объединяют с использованием кода, как показано ниже, для уменьшения диагональных столкновений (т. Е. new Foo(3,5)имеет другой хэш-код для new Foo(5,3)):

int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

О - для удобства вы можете также рассмотреть возможность предоставления ==а также !=операторы при переопределении Equalsа также GetHashCode,


Демонстрация того, что происходит, когда вы получаете это неправильно, Вот ,


1080



На самом деле это очень сложно реализовать GetHashCode()правильно, потому что, помимо уже упомянутых правил Marc, хэш-код не должен меняться в течение всего жизненного цикла объекта. Поэтому поля, которые используются для вычисления хэш-кода, должны быть неизменными.

Наконец, я нашел решение этой проблемы, когда я работал с NHibernate. Мой подход заключается в вычислении хеш-кода из идентификатора объекта. Идентификатор может быть установлен только с помощью конструктора, поэтому, если вы хотите изменить ID, что очень маловероятно, вам нужно создать новый объект с новым идентификатором и, следовательно, новый хэш-код. Этот подход лучше всего работает с GUID, потому что вы можете предоставить конструктор без параметров, который случайным образом генерирует идентификатор.


110



Переопределяя Equals, вы в основном заявляете, что именно тот, кто лучше знает, как сравнивать два экземпляра заданного типа, так что вы, вероятно, будете лучшим кандидатом на лучший хэш-код.

Это пример того, как ReSharper пишет функцию GetHashCode () для вас:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Как вы можете видеть, он просто пытается угадать хороший хеш-код, основанный на всех полях в классе, но так как вы знаете диапазон домена или значения вашего объекта, вы все равно можете обеспечить лучший.


41



Пожалуйста, не забудьте проверить параметр obj против nullпри переопределении Equals(), А также сравнить тип.

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

Причиной этого является: Equalsдолжен возвращать значение false при сравнении с null, Смотрите также http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


31



How about:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Assuming performance is not an issue :)


22



It is because the framework requires that two objects that are the same must have the same hashcode. If you override the equals method to do a special comparison of two objects and the two objects are considered the same by the method, then the hash code of the two objects must also be the same. (Dictionaries and Hashtables rely on this principle).


9



Just to add on above answers:

If you don't override Equals then the default behavior is that references of the objects are compared. The same applies to hashcode - the default implmentation is typically based on a memory address of the reference. Because you did override Equals it means the correct behavior is to compare whatever you implemented on Equals and not the references, so you should do the same for the hashcode.

Clients of your class will expect the hashcode to have similar logic to the equals method, for example linq methods which use a IEqualityComparer first compare the hashcodes and only if they're equal they'll compare the Equals() method which might be more expensive to run, if we didn't implement hashcode, equal object will probably have different hashcodes (because they have different memory address) and will be determined wrongly as not equal (Equals() won't even hit).

In addition, except the problem that you might not be able to find your object if you used it in a dictionary (because it was inserted by one hashcode and when you look for it the default hashcode will probably be different and again the Equals() won't even be called, like Marc Gravell explains in his answer, you also introduce a violation of the dictionary or hashset concept which should not allow identical keys - you already declared that those objects are essentially the same when you overrode Equals so you don't want both of them as different keys on a data structure which suppose to have a unique key. But because they have a different hashcode the "same" key will be inserted as different one.


8



We have two problems to cope with.

  1. You cannot provide a sensible GetHashCode() if any field in the object can be changed. Also often a object will NEVER be used in a collection that depends on GetHashCode(). So the cost of implementing GetHashCode() is often not worth it, or it is not possible.

  2. If someone puts your object in a collection that calls GetHashCode() and you have overrided Equals() without also making GetHashCode() behave in a correct way, that person may spend days tracking down the problem.

Therefore by default I do.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

7



Hash code is used for hash-based collections like Dictionary, Hashtable, HashSet etc. The purpose of this code is to very quickly pre-sort specific object by putting it into specific group (bucket). This pre-sorting helps tremendously in finding this object when you need to retrieve it back from hash-collection because code has to search for your object in just one bucket instead of in all objects it contains. The better distribution of hash codes (better uniqueness) the faster retrieval. In ideal situation where each object has a unique hash code, finding it is an O(1) operation. In most cases it approaches O(1).


5



It's not necessarily important; it depends on the size of your collections and your performance requirements and whether your class will be used in a library where you may not know the performance requirements. I frequently know my collection sizes are not very large and my time is more valuable than a few microseconds of performance gained by creating a perfect hash code; so (to get rid of the annoying warning by the compiler) I simply use:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(Of course I could use a #pragma to turn off the warning as well but I prefer this way.)

When you are in the position that you do need the performance than all of the issues mentioned by others here apply, of course. Most important - otherwise you will get wrong results when retrieving items from a hash set or dictionary: the hash code must not vary with the life time of an object (more accurately, during the time whenever the hash code is needed, such as while being a key in a dictionary): for example, the following is wrong as Value is public and so can be changed externally to the class during the life time of the instance, so you must not use it as the basis for the hash code:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

On the other hand, if Value can't be changed it's ok to use:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }


3



It's my understanding that the original GetHashCode() returns the memory address of the object, so it's essential to override it if you wish to compare two different objects.

EDITED: That was incorrect, the original GetHashCode() method cannot assure the equality of 2 values. Though objects that are equal return the same hash code.


0