Подтвердить что ты не робот

Переопределение GetHashCode для изменяемых объектов?

Я прочитал около 10 различных вопросов о том, когда и как переопределить GetHashCode, но я все еще кое-что не получаю. Большинство реализаций GetHashCode основаны на хэш-кодах полей объекта, но было указано, что значение GetHashCode никогда не должно меняться в течение всего жизненного цикла объекта. Как это работает, если поля, на которых он основан, являются изменяемыми? Также, если я хочу, чтобы словарные поиски и т.д. Основывались на ссылочном равенстве, а не на моем переопределенном Equals?

Я в первую очередь переопределяю Equals для простоты модульного тестирования моего кода сериализации, который, как я предполагаю, сериализует и десериализует (для XML в моем случае) убивает ссылочное равенство, поэтому я хочу убедиться, что, по крайней мере, это правильно по значению равенства, Является ли эта плохая практика отменять Equals в этом случае? В основном в большинстве исполняемых кода я хочу ссылку равенства, и я всегда использую ==, и я не переопределяю это. Должен ли я просто создать новый метод ValueEquals или что-то вместо переопределения Equals? Я предполагал, что структура всегда использует ==, а не Equals для сравнения вещей, поэтому я подумал, что было бы безопасно переопределить Equals, поскольку мне показалось, что для нее было предназначено, если вы хотите иметь второе определение равенства, отличного от оператора ==. Из чтения нескольких других вопросов, хотя кажется, что это не так.

EDIT:

Похоже, что мои намерения были неясны, я имею в виду, что в 99% случаев мне нужно простое старое ссылочное равенство, поведение по умолчанию, никаких сюрпризов. Для очень редких случаев я хочу иметь значение равенства, и я хочу явно запросить равенство значения, используя .Equals вместо ==.

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

  • Если a.Equals(b), то a.GetHashCode() должен == b.GetHashCode().
  • Значение a.GetHashCode() никогда не должно меняться для времени жизни a.

Они кажутся естественно противоречивыми, когда изменяемый объект, потому что, если состояние объекта изменяется, мы ожидаем, что значение .Equals() изменится, что означает, что GetHashCode должно измениться в соответствии с изменением в .Equals(), но GetHashCode не следует изменять.

Почему, похоже, это противоречие? Эти рекомендации не предназначены для применения к изменяемым объектам? Вероятно, предполагается, но, возможно, стоит упомянуть, что я имею в виду классы, а не структуры.

Разрешение:

Я отмечаю JaredPar как принято, но в основном для взаимодействия комментариев. Подводя итог тому, что я узнал из этого, заключается в том, что единственный способ достичь всех целей и избежать возможного причудливого поведения в крайних случаях - это переопределить Equals и GetHashCode на основе неизменяемых полей или реализовать IEquatable. Этот вид, по-видимому, уменьшает полезность переопределения Equals для ссылочных типов, поскольку из того, что я видел, большинство ссылочных типов обычно не имеют неизменяемых полей, если они не хранятся в реляционной базе данных, чтобы идентифицировать их с их первичными ключами.

4b9b3361

Ответ 1

Как это работает, если поля, на которых он основан, изменяются?

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

Также, если я хочу, чтобы словарные поиски и т.д. основывались на эталонном равенстве, а не на моем переопределенном Equals?

Пока вы реализуете интерфейс типа IEquatable<T>, это не должно быть проблемой. Большинство реализаций словаря будут выбирать сопоставитель равенства таким образом, который будет использовать IEquatable<T> над Object.ReferenceEquals. Даже без IEquatable<T>, большинство по умолчанию вызовет Object.Equals(), который затем войдет в вашу реализацию.

В основном в большинстве исполняемого кода я хочу ссылочное равенство, и я всегда использую ==, и я не переопределяю это.

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

Раньше я предполагал, что фреймворк всегда использует ==, а не Equals для сравнения вещей

То, что использует BCL, со временем изменилось. Теперь большинство случаев, которые используют равенство, будут иметь экземпляр IEqualityComparer<T> и использовать его для равенства. В тех случаях, когда один из них не указан, они будут использовать EqualityComparer<T>.Default, чтобы найти его. В худшем случае это по умолчанию вызовет Object.Equals

Ответ 2

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

Если вы хотите, чтобы поиск не использовал метод GetHashCode или Equals для класса, вы всегда можете предоставить свою собственную реализацию IEqualityComparer для использования, когда вы создаете Dictionary.

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

Ответ 3

Ничего себе, что на самом деле несколько вопросов в одном:-). Итак, один за другим:

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

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

Чтобы привести документы интерфейса Java Map:

Примечание. Следует проявлять большую осторожность, если в качестве ключей карты используются изменяемые объекты. Поведение карты не указывается, если значение объекта изменяется таким образом, который влияет на равные сравнения, пока объект является ключом на карте.

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

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

Также, если я хочу, чтобы словарные поиски и т.д. основывались на ссылочном равенстве, а не на моем переопределенном Equals?

Ну, найди словарь, который работает так. Но в стандартных библиотечных словарях используется hashcode & Equals, и нет способа изменить это.

Я в первую очередь переопределяю Equals для простоты модульного тестирования моего кода сериализации, который, как я предполагаю, сериализует и десериализует (для XML в моем случае) убивает ссылочное равенство, поэтому я хочу убедиться, что он по крайней мере правилен по значению равенства. Является ли эта плохая практика отменой Equals в этом случае?

Нет, я бы нашел это вполне приемлемым. Однако вы не должны использовать такие объекты, как ключи в словаре/хеш-таблице, поскольку они изменяемы. См. Выше.

Ответ 4

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

Короткий ответ: должны ли эти объекты уникально идентифицироваться наименьшим набором неизменяемых полей, которые могут быть использованы для этого. Это поля, которые вы должны использовать при переопределении GetHashCode и Equals.

Для тестирования совершенно разумно определять любые необходимые вам утверждения, обычно они не определяются самим типом, а скорее как утилиты в наборе тестов. Может быть, TestSuite.AssertEquals(MyClass, MyClass)?

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

Ответ 5

Я не знаю о С#, будучи относительным noob к нему, но на Java, если вы переопределяете equals(), вам нужно также переопределить hashCode(), чтобы поддерживать контракт между ними (и наоборот).. И у java тоже такая же уловка 22; в основном заставляя вас использовать неизменяемые поля... Но это проблема только для классов, которые используются как хэш-ключ, а у Java есть альтернативные реализации для всех хэш-наборов... что, возможно, не так быстро, но они эффективны позволяют использовать изменяемый объект в качестве ключа... он просто (обычно) нахмурился как "плохой дизайн".

И я чувствую, что хочу указать, что эта фундаментальная проблема вечна... Это было с тех пор, как Адам был парнем.

Я работал над кодом fortran, который старше меня (мне 36), который ломается при изменении имени пользователя (например, когда девушка выходит замуж или разводится;-)... Таким образом, инженер, The принятое решение было: Метод "GetHashCode" GetMashCode запоминает ранее вычисленный хэш-код, пересчитывает хэш-код (т.е. виртуальный маркер -dirty), и если поля ключа изменились, он возвращает null. Это приводит к тому, что кеш удаляет "грязного" пользователя (путем вызова другого GetPreviousHashCode), а затем кеш возвращает null, заставляя пользователя перечитывать из базы данных. Интересный и полезный взлом; даже если я так говорю сам; -)

Я буду использовать изменчивость (только желательно в угловых случаях) для доступа O (1) (желательно во всех случаях). Добро пожаловать в инженерное дело; страна осознанного компромисса.

Приветствия. Кит.