Вопрос

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

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

Редактировать:

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

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

  1. Если a.Equals(b) тогда a.GetHashCode() следует == b.GetHashCode().
  2. Ценность a.GetHashCode() никогда не должны меняться в течение всего срока службы a.

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

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

Разрешение:

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ваше здоровье.Кит.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top