Почему важно переопределить 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;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

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

        return base.GetHashCode();

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

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

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

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

DarthVader 20.06.2011 18:34

Прочтите это loganfranken.com/blog/692/overriding-equals-in-c-part-2

Miguel Domingues 27.01.2020 18:30

Используя визуальную студию, мы можем сгенерировать Equals () и GetHashCode () на основе свойств нашего класса. см. эту ссылку. docs.microsoft.com/en-us/visualstudio/ide/reference/…

Danushka Chathuranga 19.12.2020 22:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 527
3
397 568
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Ответ принят как подходящий

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

Метод GetHashCode () должен отражать логику Equals; правила таковы:

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

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

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

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


Демонстрация того, что происходит, когда вы ошибаетесь, - это здесь.

Могу я спросить, а почему вы размножаетесь с такими факторами?

Leandro López 16.01.2009 13:30

На самом деле, я, наверное, мог потерять одного из них; суть в том, чтобы попытаться минимизировать количество столкновений - чтобы объект {1,0,0} имел хэш, отличный от {0,1,0} и {0,0,1} (если вы понимаете, что я имею в виду ),

Marc Gravell 16.01.2009 16:45

Я изменил числа, чтобы они были понятнее (и добавил семя). В некотором коде используются разные числа - например, компилятор C# (для анонимных типов) использует начальное число 0x51ed270b и коэффициент -1521134295.

Marc Gravell 16.01.2009 16:49

@ Леандро Лопес: Обычно множители выбираются как простые числа, потому что это уменьшает количество столкновений.

Andrei Rînea 23.10.2010 03:25

«О, для удобства вы также можете рассмотреть возможность предоставления операторов == и! = При переопределении Equals и GethashCode.»: Microsoft не рекомендует реализовывать operator == для объектов, которые не являются неизменяемыми - msdn.microsoft.com/en-us/library/ms173147.aspx - «Не рекомендуется переопределять оператор == в неизменяемых типах ".

antiduh 10.05.2012 00:04

@antiduh по этому токену, также не стоит связываться с GetHashCode для неизменяемых типов, что косвенно означает не связываться с Equals! Итак: если это не ценно (независимо от class и class), не связывайтесь с равенством - разумное практическое правило.

Marc Gravell 10.05.2012 01:37

Так имеет ли смысл намеренно сделать GetHashCode менее точным, чем равно, чтобы ускорить поиск по словарю? До сих пор я всегда отражал логику, которую использую в .Equals и GetHashCode. Обновлено: ответ на мой вопрос: stackoverflow.com/questions/6305324/…

Harry Mexican 07.06.2012 12:58

Microsoft заявляет: реализация метода GetHashCode по умолчанию не гарантирует уникальных возвращаемых значений для различных объектов. Более того, .NET Framework не гарантирует реализацию метода GetHashCode по умолчанию, и значение, которое он возвращает, будет одинаковым для разных версий .NET Framework. Следовательно, реализация этого метода по умолчанию не должна использоваться в качестве уникального идентификатора объекта для целей хеширования.

user1499112 01.08.2012 06:17

@RGI в целом, GetHashCode() никогда не гарантируется между AppDomain, так что это несущественное утверждение; остальное - это просто повторение второго пункта в ответе. Реализация по умолчанию найдена для хеширование, но не может использоваться как уникальный идентификатор - все, что мы уже сказали.

Marc Gravell 01.08.2012 09:23

Так должны ли мы использовать его в любом случае, если я не получаю уникальности от getHashCode()

user1499112 01.08.2012 09:49

Не забудьте проверить, являются ли поля field1, field2 и т. д. Нулевыми, если они могут быть нулевыми.

Robert Gowland 19.09.2012 17:59

Незначительное примечание. Эта конкретная реализация вычисления хэша уязвима для генерации исключения целочисленного переполнения, если включен параметр компилятора проекта / checked. Рассмотрите возможность обертывания вычислений с непроверенным {}

Neil Moss 17.07.2013 17:34

@Neil справедливое наблюдение; По умолчанию компилятор не отмечен, но вы правы: этого не следует предполагать.

Marc Gravell 17.07.2013 22:51

Разве мы не должны проверить, являются ли field1 и field2 нулевыми при вычислении хэша, если он допускает нулевое значение?

LCJ 28.01.2014 15:29

@Lijo да, тебе стоит

Marc Gravell 28.01.2014 17:06

@ Ричард, нет, не должно

Marc Gravell 24.02.2014 22:32

Может ли кто-нибудь прокомментировать следующие правила GetHashCode (), изложенные Биллом Вагнером в Эффективном C# 4.0? Правило 1) предполагает, что objA.Equals (objB) == true не означает, что оба объекта должны возвращать один и тот же хэш-код. Это просто неверно, поскольку некоторые методы LINQ (например, Distinct ()) начинают работать некорректно. Я был очень удивлен, обнаружив такую ​​ошибку в справочнике такого калибра: «Если два объекта равны (как определено оператором ==), они должны генерировать одно и то же хэш-значение. В противном случае хеш-коды не могут использоваться для поиска объектов. в контейнерах ".

grzegorz_p 12.11.2014 16:17

@grzegorz_p, то есть наоборот; две вещи, имеющие одинаковый хэш-код, означает, что нет равны (Equals == true), однако две вещи, которые сообщаются как равные (Equals == true) должен возвращают один и тот же хэш-код, иначе они нарушили API. Вы уверены, что не просто неправильно прочитали абзац? Цитата из MSDN: «Если ваш переопределенный метод Equals возвращает true, когда два объекта проверяются на равенство, ваш переопределенный метод GetHashCode должен возвращать одно и то же значение для двух объектов».

Marc Gravell 12.11.2014 16:59

Спасибо - но что, если элемент будет использоваться в списке, а не в словаре - нужно ли в этом случае переопределить gethashcode?

BKSpurgeon 25.11.2016 05:28

@BKSpurgeon он не будет использоваться в этом случае, но это очень плохая идея переопределять Equals без переопределения GetHashCode - вы просто создаете кучу путаницы, чтобы кого-то позже поймать

Marc Gravell 25.11.2016 10:44

Он используется только в хэш-карте.

Alex78191 31.05.2017 23:39

@antiduh Я прочитал статью, которую вы связали, но она не объясняет, почему изменяемый класс не должен иметь переопределения оператора ==. Не могли бы вы или кто-нибудь еще объяснить, почему это так?

Preza8 18.08.2018 01:51

Пример кода в этом ответе даст тот же хэш-код, если вы перевернете field1 и field2. Сложение коммутативно ...

user45623 02.10.2018 21:50

@ user45623 нет - int field1 = 1, field2 = 2; // 646 - int field1 = 2, field2 = 1; // 652; кроме того, коллизии неудобно, а не инвалид

Marc Gravell 03.10.2018 00:17

@MarcGravell D'oh! Я мысленно заменил hash * 7 'на 13 * 7 для строк оба, но хеш не равен «13» во второй строке> _ <

user45623 04.10.2018 00:05

Интересный факт: Microsoft тоже ошибся.

GSerg 12.05.2019 11:49

Это связано с тем, что фреймворк требует, чтобы два одинаковых объекта имели один и тот же хэш-код. Если вы переопределите метод equals, чтобы выполнить специальное сравнение двух объектов, и эти два объекта считаются одним и тем же методом, то хэш-код этих двух объектов также должен быть одинаковым. (Словари и хеш-таблицы полагаются на этот принцип).

Переопределяя 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;
    }
}

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

Разве это не всегда будет возвращать ноль? Видимо надо инициализировать результат 1! Также нужно еще несколько точек с запятой.

Sam Mackrill 21.02.2012 18:14

Вы знаете, что делает оператор XOR (^)?

Stephen Drew 09.04.2012 15:19

Как я уже сказал, это то, что R # пишет для вас (по крайней мере, это то, что он делал еще в 2008 году), когда вас об этом просят. Очевидно, этот фрагмент предназначен для того, чтобы программист мог каким-то образом изменить его. Что касается отсутствующих точек с запятой ... да, похоже, я их пропустил, когда скопировал код из выделенного региона в Visual Studio. Я также думал, что люди поймут и то, и другое.

Trap 13.04.2012 16:11

@SamMackrill Я добавил недостающие точки с запятой.

Matthew Murdoch 04.04.2013 00:14

@SamMackrill Нет, он не всегда возвращает 0. 0 ^ a = a, значит, 0 ^ m_someVar1 = m_someVar1. С таким же успехом он мог бы установить начальное значение result на m_someVar1.

Millie Smith 16.02.2017 03:22

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

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

Я не думаю, что очень сложно реализовать код. учитывая эти правила и более подробное объяснение в эффективной книге по C#, я думаю, что переопределить GetHashCode довольно просто.

DarthVader 15.09.2010 00:08

Можете ли вы пояснить, что «хеш-код не должен изменяться в течение жизни объекта»? Это конкретный NHibernate?

vanja. 08.12.2010 06:08

@vanja. Я считаю, что это связано с тем, что если вы добавите объект в словарь, а затем измените идентификатор объекта, при извлечении позже вы будете использовать другой хеш для его получения, поэтому вы никогда не получите его из словаря.

ANeves thinks SE is evil 21.12.2010 19:18

@DarthVader, переопределить GetHashCode () несложно. И нетрудно переопределить Equals (), используя ссылочную эквивалентность (или предложение GUID Albic выше). Трудно переопределить Equals (), используя эквивалентность значений, при этом соблюдая правило, согласно которому два объекта, которые эквивалентны Equals (), должны возвращать один и тот же хэш-код: в алгоритме хэш-кода следует использовать только неизменяемое состояние объекта.

JMD 27.09.2012 00:47

В документации Microsoft по функции GetHashCode () не говорится и не подразумевается, что хэш объекта должен оставаться согласованным на протяжении всего времени существования. Фактически, в нем конкретно объясняется один допустимый случай, в котором может возникнуть нет: «Метод GetHashCode для объекта должен последовательно возвращать один и тот же хэш-код до тех пор, пока нет изменений в состоянии объекта, которое определяет возвращаемое значение метода Equals объекта. . "

PeterAllenWebb 04.10.2012 22:44

@PeterAllenWebb: Дизайн .net включает многие аспекты Java, включая некоторые ошибки. Поскольку коллекции Java не были разработаны для того, чтобы позволить пользователям указывать альтернативные методы сравнения и хэш-функции, единственный способ для коллекций рассматривать два объекта как совпадающие друг с другом - это сообщать о себе как о «равных», независимо от того, являются ли они фактически эквивалентными. Это привело к тому, что разные типы определяли «равно» для обозначения совершенно разных вещей, что очень затрудняет для объектов, которые инкапсулируют или агрегируют другие объекты универсальных типов, знать, «равны» ли они.

supercat 08.01.2013 00:26

«хэш-код не должен изменяться в течение жизни объекта» - это не так.

apocalypse 29.03.2013 15:23

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

Scott Chamberlain 11.08.2013 09:56

@ScottChamberlain Я думаю, вы НЕ забыли в своем комментарии, это должно быть: «хэш-код (ни оценка равенства) НЕ должен изменяться в течение периода, когда объект используется в качестве ключа для коллекции». Верно?

Stan Prokop 27.04.2014 23:33

За исключением того, что здесь есть недостаток: если указано ON UPDATE CASCADE, я могу изменить первичный ключ, что означает, что первичный ключ не является неизменяемым.

Stefan Steiger 09.02.2018 09:40

@StanProkop Я думаю, что комментарий Скотта Чемберлена был правильным, однако, чтобы избежать путаницы, предложение должно гласить: «хэш-код (и оценка равных) НЕ должен изменяться в течение периода, когда объект используется в качестве ключа для коллекции»

David Klempfner 23.03.2018 13:27

Как насчет:

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

Assuming performance is not an issue :)

эээ - но вы возвращаете строку для метода на основе int; _0

jim tollan 17.02.2011 15:23

Нет, он вызывает GetHashCode () из объекта String, который возвращает int.

Richard Clayton 25.04.2011 16:26

Я не ожидаю, что это будет так быстро, как хотелось бы, не только для бокса, задействованного для типов значений, но и для производительности string.Format. Еще одна интересная модель, которую я видел, - это new { prop1, prop2, prop3 }.GetHashCode(). Не могу прокомментировать, какой из этих двух будет медленнее. Не злоупотребляйте инструментами.

nawfal 15.12.2013 14:33

Это вернет истину для { prop1 = "_X", prop2 = "Y", prop3 = "Z" } и { prop1 = "", prop2 = "X_Y", prop3 = "Z_" }. Вы, наверное, этого не хотите.

voetsjoeba 02.01.2014 23:43

Да, вы всегда можете заменить символ подчеркивания чем-то менее распространенным (например, •, ▲, ►, ◄, ☺, ☻) и надеяться, что ваши пользователи не будут использовать эти символы ... :)

Ludmil Tinkov 04.09.2014 03:13

@voetsjoeba, это возможно только тогда, когда все переменные являются строковыми, но это очень хороший момент +1 :)

Timeless 09.01.2015 06:23

@voetsjoeba У вас есть опечатка: "_X_Y_Z" != "_X_Y_Z_" (лишнее подчеркивание для второго prop3), но точка взята :)

Michael 03.09.2016 01:48

@LudmilTinkov, безопаснее было бы вызвать в prop1/2/3.GetHashCode(), и тогда подчеркивание не имело бы значения.

Michael 03.09.2016 01:50

Это не обязательно важно; это зависит от размера ваших коллекций и требований к производительности, а также от того, будет ли ваш класс использоваться в библиотеке, где вы можете не знать требований к производительности. Я часто знаю, что размеры моей коллекции не очень велики, а мое время более ценно, чем несколько микросекунд производительности, достигнутой за счет создания идеального хэш-кода; поэтому (чтобы избавиться от надоедливого предупреждения компилятора) я просто использую:

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

(Конечно, я мог бы использовать #pragma, чтобы отключить предупреждение, но я предпочитаю этот способ.)

Когда вы находитесь в положении, когда вам делать нужна производительность, конечно, применяются все проблемы, упомянутые другими здесь. Самое важное - в противном случае вы получите неверные результаты при извлечении элементов из хэш-набора или словаря: хэш-код не должен меняться в зависимости от времени жизни объекта (точнее, в то время, когда требуется хеш-код, например, будучи ключом в словаре): например, следующее неправильно, поскольку Value является общедоступным и поэтому может быть изменено извне для класса в течение времени жизни экземпляра, поэтому вы не должны использовать его в качестве основы для хэш-кода:


   class A
   {
      public int Value;

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

С другой стороны, если Value нельзя изменить, можно использовать:


   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
      }
   }

Проголосовали против. Это совершенно неправильно. Даже Microsoft заявляет в MSDN (msdn.microsoft.com/en-us/library/system.object.gethashcode.‌ aspx), что значение GetHashCode ДОЛЖНО измениться, когда состояние объекта изменяется таким образом, что может повлиять на возвращаемое значение вызова Equals (), и даже в его примерах также показаны реализации GetHashCode, которые полностью зависят от на публично изменяемые ценности.

Sebastian P.R. Gingter 22.05.2013 13:27

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

ILoveFortran 24.05.2013 16:19

Себастьян, Вдобавок я не вижу в ссылке (msdn.microsoft.com/en-us/library/system.object.gethashcode.‌ aspx) утверждения, что GetHashCode () надо менять. Напротив, он НЕ должен изменяться, пока Equals возвращает одно и то же значение для одного и того же аргумента: «Метод GetHashCode для объекта должен последовательно возвращать один и тот же хэш-код до тех пор, пока не будет изменено состояние объекта, определяющее возвращаемое значение. метода Equals объекта ". Этот оператор не подразумевает обратного, что он должен измениться, если значение, возвращаемое для Equals, изменится.

ILoveFortran 24.05.2013 16:45

@ILoveFortran, я не думаю, что ты говоришь правильно. В статье MSDN четко сказано: «Хэш-код не является постоянным значением. По этой причине: 1) Не сериализуйте значения хэш-кода и не храните их в базах данных. 2) Не используйте хеш-код в качестве ключа для извлечения объекта. из коллекции с ключами. 3 ... 4 ... ", то есть HasCode изменяется в течение времени существования объекта, и ваш код должен знать об этом. Если вы хотите сделать каждый объект уникальным с помощью идентификатора, который никогда не меняется за время его существования, вам следует использовать что-то еще.

Joao Coelho 03.07.2013 07:28

@Joao, вы путаете клиентскую / потребительскую сторону контракта с производителем / исполнителем. Я говорю об ответственности разработчика, который отменяет GetHashCode (). Вы говорите о потребителе, о том, кто пользуется ценностью.

ILoveFortran 08.07.2013 19:39

Полное непонимание ... :) Правда в том, что хэш-код должен изменяться при изменении состояния объекта, если только состояние не имеет отношения к идентичности объекта. Кроме того, вы никогда не должны использовать MUTABLE в качестве ключа в ваших коллекциях. Для этого используйте объекты, доступные только для чтения. GetHashCode, Equals ... и некоторые другие методы, имена которых я не помню в данный момент, НИКОГДА не должны выбрасывать.

darlove 11.10.2017 14:28

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

supercat 07.11.2020 23:45

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

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

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

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

Эта проверка типа завершится неудачно в ситуации, когда подкласс ссылается на метод Equals суперкласса как часть своего собственного сравнения (т.е. base.Equals (obj)) - вместо этого следует использовать as

sweetfa 21.08.2012 06:55

@sweetfa: Это зависит от того, как реализован метод Equals подкласса. Он также может вызвать base.Equals ((BaseType) obj)), который будет работать нормально.

huha 27.08.2013 14:03

Нет, не будет: msdn.microsoft.com/en-us/library/system.object.gettype.aspx. Кроме того, реализация метода не должна завершаться неудачей или завершаться успешно в зависимости от способа его вызова. Если тип среды выполнения объекта является подклассом некоторого базового класса, тогда Equals () базового класса должен возвращать истину, если obj действительно равен this, независимо от того, как был вызван Equals () базового класса.

Jupiter 25.09.2013 00:57

Перемещение fooItem наверх с последующей проверкой на нуль будет работать лучше в случае нулевого или неправильного типа.

IS4 06.02.2017 14:06

@ IllidanS4, если вы не работаете с типом значения. Но тогда зачем вам переопределять Equals типа значения.

S1r-Lanzelot 15.02.2018 18:36

@ 40Alpha Ну да, тогда obj as Foo был бы недействителен.

IS4 15.02.2018 22:09

Хеш-код используется для коллекций на основе хешей, таких как Dictionary, Hashtable, HashSet и т. д. Цель этого кода - очень быстро предварительно отсортировать определенный объект, поместив его в определенную группу (ведро). Эта предварительная сортировка очень помогает в поиске этого объекта, когда вам нужно получить его обратно из коллекции хешей, потому что код должен искать ваш объект только в одной корзине, а не во всех содержащихся в ней объектах. Чем лучше распределение хэш-кодов (лучшая уникальность), тем быстрее поиск. В идеальной ситуации, когда каждый объект имеет уникальный хэш-код, его поиск занимает O (1) операция. В большинстве случаев он приближается к O (1).

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

ИЗМЕНИТЬ: Это было неверно, исходный метод GetHashCode () не может гарантировать равенство двух значений. Хотя равные объекты возвращают один и тот же хэш-код.

У нас есть две проблемы, с которыми нужно справиться.

  1. Вы не можете предоставить толковый GetHashCode(), если какое-либо поле в объект можно изменить. Также часто объект НИКОГДА не будет использоваться в сборник, зависящий от GetHashCode(). Так что стоимость внедрение GetHashCode() часто не стоит или не стоит возможный.

  2. Если кто-то помещает ваш объект в коллекцию, которая вызывает GetHashCode(), и вы переопределили Equals(), не выполняя при этом GetHashCode() вести себя правильно, этот человек может проводить дни отслеживание проблемы.

Поэтому по умолчанию делаю.

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

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

        if (fooItem == null)
        {
           return false;
        }

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

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

supercat 19.12.2013 11:11

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

Ian Ringrose 19.12.2013 13:41

Раньше я делал что-то подобное для всех определенных мною классов, которым нужен Equals (), и где я был полностью уверен, что никогда не буду использовать этот объект в качестве ключа в коллекции. Затем однажды программа, в которой я использовал такой объект в качестве входных данных для элемента управления DevExpress XtraGrid, потерпела крах. Оказывается, XtraGrid за моей спиной создавал HashTable или что-то в этом роде на основе моих объектов. У меня возник небольшой спор по этому поводу с людьми из службы поддержки DevExpress. Я сказал, что это неразумно, что они основывали функциональность и надежность своих компонентов на неизвестной клиентской реализации неясного метода.

RenniePet 29.06.2014 08:27

Люди DevExpress были довольно язвительными, в основном говоря, что я должен быть идиотом, чтобы генерировать исключение в методе GetHashCode (). Я все еще думаю, что им следует найти альтернативный метод выполнения того, что они делают - я вспоминаю, как Марк Грейвелл в другом потоке описывал, как он строит словарь произвольных объектов, не зависящий от GetHashCode () - не могу вспомнить, как он это сделал. хоть.

RenniePet 29.06.2014 08:32

@RenniePet, должно быть, лучше иметь увлечение из-за выброса исключения, а затем очень сложно найти ошибку из-за недопустимой реализации.

Ian Ringrose 29.06.2014 23:53

Да, я тоже так относился к проблеме до DevExpress XtraGrid. Теперь я просто намеренно пренебрегаю написанием методов GetHashCode () и подавляю предупреждения во время компиляции от Code Analysis и ReSharper.

RenniePet 30.06.2014 00:58

Урок здесь: никогда и ни для чего не используйте DevExpress.

Suamere 28.06.2020 23:30

Ниже использование отражения кажется мне лучшим вариантом с учетом общедоступных свойств, поскольку при этом вам не нужно беспокоиться о добавлении / удалении свойств (хотя и не так распространенный сценарий). Я обнаружил, что это тоже работает лучше (по сравнению со временем с использованием секундомера Diagonistics).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if (value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }

Ожидается, что реализация GetHashCode () будет очень легкой. Я не уверен, что использование отражения заметно с помощью StopWatch на тысячах вызовов, но оно наверняка присутствует на миллионах (подумайте о заполнении словаря из списка).

bohdan_trotsenko 12.12.2014 12:45

Просто чтобы добавить ответы выше:

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

Клиенты вашего класса будут ожидать, что хэш-код будет иметь логику, аналогичную методу equals, например методы linq, которые используют IEqualityComparer, сначала сравнивают хэш-коды, и только если они равны, они будут сравнивать метод Equals (), который может быть более дорогим. для запуска, если мы не реализовали хэш-код, равный объект, вероятно, будет иметь разные хэш-коды (потому что у них другой адрес в памяти) и будет ошибочно определен как не равный (Equals () даже не сработает).

Кроме того, за исключением проблемы, связанной с тем, что вы не сможете найти свой объект, если использовали его в словаре (потому что он был вставлен одним хэш-кодом, и когда вы его ищите, хэш-код по умолчанию, вероятно, будет другим, и снова Equals () даже не будет вызван, как объясняет Марк Гравелл в своем ответе, вы также вводите нарушение концепции словаря или хеш-набора, которое не должно допускать идентичных ключей - вы уже заявили, что эти объекты по существу одинаковы, когда вы переопределяете Equals, поэтому вы не хотите, чтобы они оба были разными ключами в структуре данных, которая предполагает наличие уникального ключа. Но поскольку у них другой хэш-код, «тот же» ключ будет вставлен как другой.

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

Например, предположим, что мы храним некоторые объекты в списке. Некоторое время спустя кто-то действительно понимает, что HashSet - намного лучшая альтернатива, например, из-за лучших характеристик поиска. Вот тогда мы можем попасть в беду. List будет внутренне использовать компаратор равенства по умолчанию для типа, что означает Equals в вашем случае, в то время как HashSet использует GetHashCode (). Если они ведут себя по-разному, ваша программа тоже. И имейте в виду, что такие проблемы не так просто устранить.

Я суммировал это поведение с некоторыми другими ловушками GetHashCode () в Сообщение блога, где вы можете найти дополнительные примеры и объяснения.

Что касается .NET 4.7, предпочтительный метод замены GetHashCode() показан ниже. Если нацелены на более старые версии .NET, включите пакет System.ValueTuple nuget.

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

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

Начиная с C# 9 (.net 5 или .net core 3.1), вы можете захотеть использовать записи, как это делает Равенство, основанное на ценностях.

Другие вопросы по теме