Какова лучшая стратегия для Equals и GetHashCode? - PullRequest
34 голосов
/ 02 марта 2010

Я работаю с моделью предметной области и размышлял о различных способах реализации этих двух методов в .NET. Какова ваша предпочтительная стратегия?

Это моя текущая реализация:

public override bool Equals(object obj)
{
    var newObj = obj as MyClass;

    if (null != newObj)
    {
        return this.GetHashCode() == newObj.GetHashCode();
    }
    else
    {
        return base.Equals(obj);
    }
}

// Since this is an entity I can use its Id
// When I don't have an Id, I usually make a composite key of the properties
public override int GetHashCode()
{
    return String.Format("MyClass{0}", this.Id.ToString()).GetHashCode();
}

Ответы [ 5 ]

32 голосов
/ 02 марта 2010

Домен-управляемый дизайн проводит различие между Сущностями и Объектами значения . Это хорошее различие, которое нужно наблюдать, поскольку оно определяет, как вы реализуете Equals.

Сущности равны, если их идентификаторы равны друг другу.

Объекты значения равны, если все их (важные) составляющие элементы равны друг другу.

В любом случае реализация GetHashCode должна основываться на тех же значениях, которые используются для определения равенства. Другими словами, для сущностей хэш-код должен рассчитываться непосредственно из идентификатора, тогда как для объектов-значений он должен рассчитываться по всем составляющим значениям.

5 голосов
/ 21 января 2016

Ни один из ответов здесь действительно не подходит мне. Поскольку вы уже сказали, что вы не можете использовать Id для равенства, и вам нужно использовать набор свойств, вот лучший способ сделать это. Примечание: я не считаю, что в целом это лучший способ реализовать Equals и GetHashCode. Это лучшая версия кода ОП.

public override bool Equals(object obj) {
   var myClass = obj as MyClass;

   if (myClass != null) {
      // Order these by the most different first.
      // That is, whatever value is most selective, and the fewest
      // instances have the same value, put that first.
      return this.Id == myClass.Id
         && this.Name == myClass.Name
         && this.Quantity == myClass.Quantity
         && this.Color == myClass.Color;
   } else {
      // This may not make sense unless GetHashCode refers to `base` as well!
      return base.Equals(obj);
   }
}

public override int GetHashCode() {
   int hash = 19;
   unchecked { // allow "wrap around" in the int
      hash = hash * 31 + this.Id; // assuming integer
      hash = hash * 31 + this.Name.GetHashCode();
      hash = hash * 31 + this.Quantity; // again assuming integer
      hash = hash * 31 + this.Color.GetHashCode();
   }
   return hash;
}

См. этот ответ Джона Скита , чтобы узнать некоторые причины этого. Использование xor не годится, потому что различные наборы данных могут в итоге привести к одному и тому же хешу. Этот метод обтекания с простыми числами (начальные значения 19 и 31 выше или другие выбранные вами значения) лучше выполняет сегментацию в «сегменты», каждый из которых имеет несколько коллизий.

Если какое-либо из ваших значений может быть нулевым, я призываю вас тщательно продумать, как они должны сравниваться. Вы могли бы использовать нулевую оценку короткого замыкания и оператор объединения нулей, возможно. Но убедитесь, что, если значения NULL сравниваются как равные, вы назначаете разные хеш-коды различным свойствам, допускающим значение NULL, когда они равны NULL.

Кроме того, я не уверен, что ваша реализация Equals имеет какой-то смысл. Когда два объекта сравниваются на равенство, сначала сравниваются их значения GetHashCode. Только если они различаются, запускается метод Equals (поэтому, если два объекта, хэширующие одно и то же значение, различаются, это будет обнаружено). Поскольку ваша реализация GetHashCode не ссылается на base, для вашего Equals метода может не иметь смысла делать это. В частности, у вас будет серьезная ошибка, ожидающая поломки, если Equals может вернуть true для двух объектов, хеш-коды которых различны.

2 голосов
/ 27 марта 2018

Я наткнулся на этот старый вопрос и, ИМХО, я не нашел ни одного четкого и простого ответа на поставленный первоначальный вопрос, сформулированный @ tucaz.

Я могу согласиться со многими соображениями, изложенными выше (или ниже: D), но «вопрос» был пропущен (я думаю).

При условии, что:

  • Равенство требуется для субъектов
  • Объекты-сущности можно считать равными, если они отображают одну и ту же сущность, если они ссылаются на один и тот же «Ключ сущности»
  • В примере, показанном @tucaz, просто упоминается «Id» (см. Слишком реализованный GetHashCode ())… не говоря уже о глючном Equals (…)

Я могу предположить, что одной простой реализацией может быть:

public class MyEntity: IEquatable<MyEntity> {
    int Id;

    public MyEntity(int id){
        Id = id;
    }

    public override bool Equals(object obj) => Equals(obj as MyEntity);
    public bool Equals(MyEntity obj) => obj != null && Id == obj.Id;
    public override int GetHashCode() => Id;
}

Вот и все!

2 голосов
/ 02 марта 2010

Хеш-коды могут сталкиваться, поэтому я не думаю, что они являются хорошим способом сравнения равенства. Вы должны сравнить базовые значения, которые делают объекты "равными". См. Ответ @Jon Skeet на этот вопрос: Каков наилучший алгоритм для переопределенного System.Object.GetHashCode? для лучшей реализации GetHashCode, если ваше равенство охватывает несколько свойств. Если это просто одно свойство, вы можете просто повторно использовать его хеш-код.

1 голос
/ 02 марта 2010

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

Полагаю, ваша реализация GetHashCode в порядке, но я обычно использую вещи, подобные этим:

public override int GetHashCode() {
    return object1.GetHashCode ^ intValue1 ^ (intValue2 << 16);
}
...