Прошло много времени, но, тем не менее, я думаю, что все еще необходимо дать правильный ответ на этот вопрос, включая объяснения, почему и как. Лучший ответ на данный момент - это тот, который цитирует MSDN исчерпывающе - не пытайтесь создавать свои собственные правила, ребята из MS знали, что они делают.
Но обо всем по порядку. Указанное в этом вопросе неправильное указание.
Теперь почему - их два
Во-первых, почему : если хеш-код вычисляется таким образом, что он не изменяется в течение жизни объекта, даже если сам объект изменяется, то это нарушит контракт равенства.
Помните: «Если два объекта сравниваются как равные, метод GetHashCode для каждого объекта должен возвращать одно и то же значение. Однако, если два объекта не сравниваются как равные, методы GetHashCode для двух объектов не должны возвращать разные значения».
Второе предложение часто неверно интерпретируется как «Единственное правило заключается в том, что во время создания объекта хэш-код одинаковых объектов должен быть равен». Не знаю почему, но в этом суть большинства ответов.
Подумайте о двух объектах, содержащих имя, где имя используется в методе equals: «То же имя -> то же самое». Создать экземпляр A: Имя = Джо Создать экземпляр B: Имя = Питер
Хэш-код A и Hashcode B, скорее всего, не будут совпадать. Что теперь произойдет, когда имя экземпляра B изменится на Joe?
Согласно руководству из вопроса, хэш-код B не изменится. Результатом этого будет: A.Equals (B) ==> true Но в то же время: A.GetHashCode () == B.GetHashCode () ==> false.
Но именно это поведение явно запрещено контрактом equals & hashcode.
Во-вторых, почему : хотя, конечно, верно, что изменения в хеш-коде могут нарушить хешированные списки и другие объекты, использующие хеш-код, обратное также верно. Если не изменить хеш-код, в худшем случае получатся хешированные списки, где все множество различных объектов будут иметь одинаковый хеш-код и, следовательно, находиться в одном и том же хэш-бине - это происходит, например, когда объекты инициализируются стандартным значением.
Теперь перейдем к практическим рекомендациям. На первый взгляд кажется, что здесь есть противоречие - в любом случае код сломается. Но ни одна из проблем не связана с измененным или неизменным хеш-кодом.
Источник проблем хорошо описан в MSDN:
Из записи хеш-таблицы MSDN:
Ключевые объекты должны быть неизменяемыми, если они используются в качестве ключей в Hashtable.
Это значит:
Любой объект, который создает значение hashvalue, должен изменять значение hashvalue, когда объект изменяется, но он не должен - абсолютно не должен - разрешать какие-либо изменения самому себе, когда он используется внутри Hashtable (или любого другого объекта, использующего Hash, конечно) ,
Во-первых, как проще всего было бы, конечно, проектировать неизменяемые объекты только для использования в хеш-таблицах, которые будут создаваться как копии обычных, изменяемых объектов при необходимости. Внутри неизменяемых объектов совершенно очевидно, что кэшировать хеш-код вполне нормально, поскольку он неизменен.
Во-вторых, как Или передайте объекту флаг «Вы хэшированы сейчас», убедитесь, что все данные объекта являются частными, проверьте флажок во всех функциях, которые могут изменять данные объекта, и сгенерируйте данные исключения, если изменение не разрешено (т. Е. Установлен флаг ). Теперь, когда вы помещаете объект в любую область хеширования, убедитесь, что вы установили флаг, а также - сбросили флаг, когда он больше не нужен. Для простоты использования я бы посоветовал автоматически установить флаг внутри метода «GetHashCode» - так его нельзя забыть. А явный вызов метода «ResetHashFlag» гарантирует, что программисту придется думать, разрешено ли ему изменять данные объектов на данный момент.
Хорошо, что также следует сказать: есть случаи, когда возможно иметь объекты с изменяемыми данными, когда хеш-код, тем не менее, не изменяется, когда данные объектов изменяются, не нарушая контракт equals & hashcode-contract.
Это, однако, требует, чтобы метод equals также не основывался на изменчивых данных. Итак, если я напишу объект и создаю метод GetHashCode, который вычисляет значение только один раз и сохраняет его внутри объекта, чтобы вернуть его при последующих вызовах, то я снова должен: абсолютно необходимо создать метод Equals, который будет использовать сохраненные значения для сравнения, так что A.Equals (B) никогда не изменится с ложного на истинное. В противном случае договор будет нарушен. Результатом этого обычно будет то, что метод Equals не имеет никакого смысла - это не исходная ссылка equals, но это также и не значение equals. Иногда это может быть предполагаемое поведение (например, записи клиентов), но обычно это не так.
Итак, просто сделайте изменение результата GetHashCode, когда данные объекта изменятся, и если использование объекта внутри хеша с использованием списков или объектов предназначено (или просто возможно), тогда сделайте объект либо неизменным, либо создайте флаг только для чтения, чтобы использовать его для время жизни хешированного списка, содержащего объект.
(Между прочим: все это не является специфичным для C # oder .NET - в природе всех реализаций хеш-таблиц или, в более общем смысле, любого индексированного списка, идентифицирующие данные объектов никогда не должны изменяться, пока объект находится в списке Неожиданное и непредсказуемое поведение произойдет, если это правило будет нарушено. Где-то могут быть реализации списка, которые отслеживают все элементы в списке и выполняют автоматическую переиндексацию списка, но их производительность в лучшем случае будет ужасной.)