Рекомендации GetHashCode в С#

Я прочитал в книге Essential С# 3.0 и .NET 3.5, что:

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

Является ли это допустимым ориентиром?

Я пробовал пару встроенных типов в .NET, и они не вели себя так.

Ответ 1

Ответ в основном - это правильное руководство, но, возможно, не допустимое правило. Он также не рассказывает всю историю.

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

Например, объект A возвращает хэш 1. Таким образом, он попадает в ячейку 1 хеш-таблицы. Затем вы меняете объект A таким образом, чтобы он возвращал хэш из 2. Когда хеш-таблица ищет его, он выглядит в бункере 2 и не может его найти - объект осиротет в ящике 1. Вот почему хеш-код должен не изменяйте для времени жизни объекта, и только одна причина, по которой создание реализаций GetHashCode является болью в прикладе.

Обновление
Эрик Липперт опубликовал блог, который дает отличную информацию о GetHashCode.

Дополнительное обновление
Я сделал несколько изменений выше:

  • Я сделал различие между руководством и правилом.
  • Я пробил "за время жизни объекта".

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

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

Ответ 2

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

Но сначала сначала: Указанное в этом вопросе правило неверно.

Теперь whys - есть два из них

Сначала почему: Если хеш-код вычисляется таким образом, что он не изменяется во время жизни объекта, даже если сам объект изменяется, чем он разорвал бы равен-контракт.

Помните: "Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode для двух объектов не должны возвращать разные значения".

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

Подумайте о двух объектах, содержащих имя, где имя используется в методе equals: То же имя → то же самое. Создать экземпляр A: Name = Joe Создать экземпляр B: Name = Peter

Hashcode A и Hashcode B, скорее всего, не будут одинаковыми. Что произойдет, когда имя экземпляра B будет изменено на Joe?

В соответствии с директивой из вопроса хэш-код B не изменится. Результатом этого будет: A.Equals(B) == > true Но в то же время: A.GetHashCode() == B.GetHashCode() == > false.

Но именно это поведение запрещено явно с помощью equs & hashcode-contract.

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


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

Источник проблем хорошо описан в MSDN:

Из записи хэш-таблицы MSDN:

Ключевые объекты должны быть неизменными как долго поскольку они используются как ключи в Hashtable.

Это означает:

Любой объект, создающий hashvalue, должен изменять значение hashvalue, когда объект изменяется, но он не должен - абсолютно не должен - разрешать любые изменения самому себе, когда он используется внутри Hashtable (или любого другого объекта Hash-using, конечно).

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

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

Хорошо, что тоже нужно сказать: бывают случаи, когда возможно иметь объекты с изменяемыми данными, где хеш-код, тем не менее, не изменяется, когда данные объектов изменяются, не нарушая equals & hashcode-contract.

Однако для этого требуется, чтобы метод equals не основывался на изменяемых данных. Итак, если я пишу объект и создаю метод GetHashCode, который вычисляет значение только один раз и сохраняет его внутри объекта, чтобы возвращать его при последующих вызовах, тогда я должен снова: абсолютно необходимо создать метод Equals, который будет использовать сохраненные значения для сравнения, так что A.Equals(B) никогда не изменится с false на true. В противном случае договор будет разорван. Результатом этого будет, как правило, то, что метод Equals не имеет никакого смысла - он не является исходной ссылкой равным, но он не равен и значению. Иногда это может быть предполагаемое поведение (например, записи клиентов), но обычно это не так.

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

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

Ответ 3

От MSDN

Если два объекта сравниваются как равные, Метод GetHashCode для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равно, методы GetHashCode для два объекта не должны возвращаться разные значения.

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

Для лучшей производительности хэш функция должна генерировать случайную распределение для всех входных данных.

Это означает, что при изменении значения (ов) объекта хеш-код должен измениться. Например, класс "Человек" с свойством "Имя", установленным в "Том", должен иметь один хэш-код и другой код, если вы измените имя на "Джерри". В противном случае Том == Джерри, который, вероятно, не был тем, что вы бы намеревались.


Изменить:

Также из MSDN:

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

Из запись в хэш-таблице MSDN:

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

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

В примере System.Drawing.Point объект изменен и возвращает другой хэш-код при изменении значения X или Y. Это сделало бы его бедным кандидатом, который будет использоваться как есть в хэш-таблице.

Ответ 4

Я думаю, что документация относительно GetHashcode немного запутанна.

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

MSDN:

Функция хеширования должна иметь следующие свойства:

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

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

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

Эта реализация уже нарушает правила, которые можно найти в MSDN. Предположим, у вас есть 2 экземпляра этого класса; для свойства Name экземпляра instance1 установлено значение "Pol", а свойство Name экземпляра2 установлено в "Piet". Оба экземпляра возвращают другой хэш-код, и они также не равны. Предположим, что я изменяю имя экземпляра2 на "Pol", а затем, согласно моему методу Equals, оба экземпляра должны быть равны, и согласно одному из правил MSDN они должны возвращать тот же хэш-код.
Однако это невозможно сделать, поскольку хэш-код экземпляра2 изменится, и MSDN заявляет, что это не разрешено.

Затем, если у вас есть сущность, вы можете реализовать хэш-код, чтобы он использовал "первичный идентификатор" этого объекта, который, возможно, идеально подходит для суррогатного ключа или неизменяемого свойства. Если у вас есть объект value, вы можете реализовать Hashcode, чтобы он использовал "свойства" этого объекта значения. Эти свойства составляют "определение" объекта значения. Это, конечно, характер объекта ценности; вы не заинтересованы в ее идентичности, а скорее в ее ценности.
И поэтому объекты ценности должны быть неизменными. (Так же, как в среде .NET, строка, Date и т.д. - все неизменяемые объекты).

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

Ответ 5

Это хороший совет. Вот что Брайан Пепин должен сказать по этому поводу:

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

Ответ 6

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

Ответ 7

Посмотрите это сообщение в блоге от Marc Brooks:

VTO, RTO и GetHashCode() - о, мой!

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

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

Ответ 8

Хэш-код никогда не изменяется, но также важно понять, откуда происходит хэш-код.

Если ваш объект использует семантику значения, то есть идентификатор объекта определяется его значениями (например, String, Color, all structs). Если ваш идентификатор объекта не зависит от всех его значений, то Hashcode идентифицируется подмножеством его значений. Например, ваша запись StackOverflow где-то хранится в базе данных. Если вы измените свое имя или адрес электронной почты, ваша клиентская позиция останется прежней, хотя некоторые значения изменились (в конечном итоге вас обычно идентифицирует какой-то длинный идентификатор клиента).

Короче говоря:

Семантика типа значения - Hashcode определяется значениями Стилизация ссылочного типа - Hashcode определяется некоторым id

Я предлагаю вам прочитать проект Driven Design, разработанный Эриком Эвансом (Eric Evans), где он переходит в сущности и типы значений (что более или менее то, что я пытался сделать выше), если это все еще не имеет смысла.