Переопределить GetHashCode для изменяемых объектов?
Я прочитал около 10 различных вопросов о том, когда и как переопределить GetHashCode
но есть еще кое-что, что я не совсем понимаю. Большинство реализаций GetHashCode
основаны на хэш-кодах полей объекта, но было указано, что значение GetHashCode
никогда не должен меняться в течение срока службы объекта. Как это работает, если поля, на которых он основан, являются изменяемыми? И что делать, если я хочу, чтобы поиск по словарю и т. Д. Был основан на равенстве ссылок, а не на моем Equals
?
Я в первую очередь переопределяю Equals
для удобства модульного тестирования мой код сериализации, который я предполагаю, что сериализация и десериализация (в моем случае XML) убивает ссылочное равенство, поэтому я хочу убедиться, что он по крайней мере верен по значению равенства. Это плохая практика, чтобы переопределить Equals
в этом случае? В основном в большинстве исполняемого кода я хочу ссылочного равенства и всегда использую ==
и я не переопределяю это. Должен ли я просто создать новый метод ValueEquals
или что-то вместо переопределения Equals
? Я привык предполагать, что фреймворк всегда использует ==
и не Equals
сравнивать вещи и поэтому я думал, что это было безопасно переопределить Equals
так как мне показалось, что его целью было, если вы хотите иметь второе определение равенства, которое отличается от ==
оператор. Из чтения нескольких других вопросов, хотя кажется, что это не так.
РЕДАКТИРОВАТЬ:
Кажется, мои намерения были неясны, я имею в виду, что в 99% случаев я хочу получить простое равенство ссылок, поведение по умолчанию, никаких сюрпризов. В очень редких случаях я хочу иметь равенство значений и явно запрашивать равенство значений, используя .Equals
вместо ==
,
Когда я делаю это, компилятор рекомендует переопределить GetHashCode
и вот так возник этот вопрос. Казалось, что есть противоречивые цели для GetHashCode
применительно к изменяемым объектам:
- Если
a.Equals(b)
затемa.GetHashCode()
должен== b.GetHashCode()
, - Значение
a.GetHashCode()
никогда не должен меняться в течение жизниa
,
Это кажется естественным противоречием, когда изменяемый объект, потому что, если состояние объекта изменяется, мы ожидаем, что значение .Equals()
изменить, что означает, что GetHashCode
должен измениться, чтобы соответствовать изменению в .Equals()
, но GetHashCode
не должно меняться.
Почему, кажется, существует это противоречие? Разве эти рекомендации не предназначены для применения к изменчивым объектам? Вероятно, предполагается, но, возможно, стоит упомянуть, что я имею в виду классы, а не структуры.
Разрешение:
Я отмечаю JaredPar как принятый, но в основном для взаимодействия с комментариями. Подводя итог, я понял из этого, что единственный способ достичь всех целей и избежать возможного странного поведения в крайних случаях - это только переопределить Equals
а также GetHashCode
на основе неизменяемых полей или реализации IEquatable
, Этот вид, кажется, уменьшает полезность переопределения Equals
для ссылочных типов, как я видел, большинство ссылочных типов обычно не имеют неизменяемых полей, если они не хранятся в реляционной базе данных, чтобы идентифицировать их с их первичными ключами.
5 ответов
Как это работает, если поля, на которых он основан, являются изменяемыми?
Это не означает, что хеш-код будет меняться при изменении объекта. Это проблема по всем причинам, перечисленным в статьях, которые вы прочитали. К сожалению, это тип проблемы, который обычно проявляется только в угловых случаях. Таким образом, разработчики склонны избегать плохого поведения.
Кроме того, что если я хочу, чтобы поиск по словарю и т. Д. Основывался на равенстве ссылок, а не на моих переопределенных параметрах Equals?
Пока вы реализуете интерфейс как IEquatable<T>
это не должно быть проблемой. Большинство реализаций словаря выбирают компаратор равенства способом, который будет использовать IEquatable<T>
над Object.ReferenceEquals. Даже без IEquatable<T>
Большинство по умолчанию вызовет Object.Equals(), который затем войдет в вашу реализацию.
В основном в большинстве исполняемого кода я хочу ссылочного равенства и всегда использую ==, и я не переопределяю это.
Если вы ожидаете, что ваши объекты будут вести себя с равенством значений, вы должны переопределить == и!=, Чтобы обеспечить равенство значений для всех сравнений. Пользователи по-прежнему могут использовать Object.ReferenceEquals, если они действительно хотят равенство ссылок.
Раньше я предполагал, что каркас всегда использует ==, а не равно для сравнения вещей
То, что использует BCL, со временем немного изменилось. Теперь большинство случаев, которые используют равенство, IEqualityComparer<T>
экземпляр и использовать его для равенства. В тех случаях, когда один не указан, они будут использовать EqualityComparer<T>.Default
найти. В худшем случае это по умолчанию вызовет Object.Equals
Если у вас есть изменяемый объект, нет смысла переопределять метод GetHashCode, так как вы не можете его использовать. Это используется, например, Dictionary
а также HashSet
коллекции для размещения каждого предмета в ведре. Если вы измените объект, когда он используется в качестве ключа в коллекции, хеш-код больше не будет соответствовать корзине, в которой находится объект, поэтому коллекция не будет работать должным образом, и вы больше никогда не сможете найти объект.
Если вы хотите, чтобы поиск не использовал GetHashCode
или же Equals
метод класса, вы всегда можете предоставить свой собственный IEqualityComparer
реализация использовать вместо этого при создании Dictionary
,
Equals
Метод предназначен для равноправия, поэтому его реализация не является ошибкой.
Вау, это на самом деле несколько вопросов в одном:-). Итак, один за другим:
было указано, что значение GetHashCode никогда не должно меняться в течение всего времени жизни объекта. Как это работает, если поля, на которых он основан, являются изменяемыми?
Этот общий совет предназначен для случая, когда вы хотите использовать свой объект в качестве ключа в HashTable / словаре и т. Д. HashTables обычно требуют, чтобы хэш не менялся, потому что они используют его, чтобы решить, как сохранить и получить ключ. Если хэш изменится, HashTable, вероятно, больше не найдет ваш объект.
Чтобы процитировать документы интерфейса Java на карте:
Примечание: следует соблюдать особую осторожность, если в качестве ключей карты используются изменяемые объекты. Поведение карты не указывается, если значение объекта изменяется таким образом, что это влияет на сравнения равных, в то время как объект является ключом на карте.
В общем, плохая идея использовать любой вид изменяемого объекта в качестве ключа в хэш-таблице: даже неясно, что должно произойти, если ключ изменяется после его добавления в хэш-таблицу. Должна ли хеш-таблица возвращать сохраненный объект через старый ключ, или через новый ключ, или через оба?
Итак, реальный совет: используйте только неизменяемые объекты в качестве ключей и убедитесь, что их хеш-код также никогда не изменяется (что обычно происходит автоматически, если объект неизменен).
Кроме того, что если я хочу, чтобы поиск по словарю и т. Д. Основывался на равенстве ссылок, а не на моих переопределенных параметрах Equals?
Ну, найдите словарную реализацию, которая работает так. Но стандартные словари библиотеки используют хэш-код и Equals, и изменить это невозможно.
В первую очередь я переопределяю Equals для простоты модульного тестирования моего кода сериализации, который, как я предполагаю, сериализацией и десериализацией (в моем случае, XML) убивает ссылочное равенство, поэтому я хочу убедиться, что он по крайней мере корректен по равенству значений. Это плохая практика переопределять Equals в этом случае?
Нет, я нахожу это вполне приемлемым. Однако вы не должны использовать такие объекты как ключи в словаре / хэш-таблице, поскольку они изменчивы. Смотри выше.
Я не знаю, как C# является относительной нубой к нему, но в Java, если вы переопределяете equals(), вам также необходимо переопределить hashCode(), чтобы поддерживать контракт между ними (и наоборот)... И Java также имеет такой же улов 22; принуждая вас использовать неизменяемые поля... Но это проблема только для классов, которые используются в качестве хеш-ключа, а в Java есть альтернативные реализации для всех коллекций на основе хеша... что, возможно, не так быстро, но они работают эффективно позволяет вам использовать изменяемый объект в качестве ключа... он просто (обычно) выглядит как "плохой дизайн".
И я чувствую желание указать, что эта фундаментальная проблема вечна... Это было с тех пор, как Адам был парнем.
Я работал над кодом Fortran, который старше меня (мне 36), который ломается, когда имя пользователя меняется (например, когда девушка выходит замуж или развелась;-) ... Таким образом, инженерия, Принятое решение было: "Метод" GetHashCode запоминает ранее вычисленный hashCode, пересчитывает hashCode (т. Е. Виртуальный маркер isDirty) и, если ключевые поля изменились, возвращает нуль. Это приводит к тому, что кеш удаляет "грязного" пользователя (путем вызова другого GetPreviousHashCode), а затем кеш возвращает ноль, в результате чего пользователь перечитывает данные из базы данных. Интересный и стоящий взломать; даже если я сам так скажу;-)
Я поменяю изменчивость (желательно только в угловых случаях) для доступа O(1) (желательно во всех случаях). Добро пожаловать в инжиниринг; земля осознанного компромисса.
Приветствия. Кит.
Основная тема здесь - как наилучшим образом идентифицировать объекты. Вы упоминаете сериализацию / десериализацию, которая важна, потому что ссылочная целостность теряется в этом процессе.
Краткий ответ: уникальна ли идентификация объектов по наименьшему набору неизменяемых полей, которые можно использовать для этого? Это поля, которые вы должны использовать при переопределении GetHashCode и Equals.
Для тестирования вполне разумно определить любые утверждения, которые вам нужны, обычно они определяются не для самого типа, а скорее как служебные методы в наборе тестов. Может быть, TestSuite.AssertEquals(MyClass, MyClass)?
Обратите внимание, что GetHashCode и Equals должны работать вместе. GetHashCode должен возвращать одинаковое значение для двух объектов, если они равны. Функция Equals должна возвращать true, если и только если два объекта имеют одинаковый хэш-код. (Обратите внимание, что два объекта могут не совпадать, но могут возвращать один и тот же хэш-код). Существует множество веб-страниц, посвященных этой теме, просто Google.