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


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

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

EDIT:

кажется, мои намерения были неясно, что я имею в виду, что в 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 для ссылочных типов, как из того, что я видел, большинство ссылочных типов обычно не имеют неизменяемых полей, если они не хранятся в реляционной базе данных, чтобы идентифицировать их с их первичными ключами.

5 52

5 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ура. Кит.