Инструкции GetHashCode в C#

134
задан Michał Powaga 7 May 2012 в 11:25
поделиться

8 ответов

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

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

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

Обновление
Eric Lippert отправил блог , который дает превосходную информацию о GetHashCode.

Дополнительное Обновление
я внес несколько изменений выше:

  1. я сделал различие между инструкцией и правилом.
  2. я перечеркнул "в течение времени жизни объекта".

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

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

92
ответ дан 23 November 2019 в 23:55
поделиться

От MSDN

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

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

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

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

<час>

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

Также из MSDN:

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

От запись хеш-таблицы MSDN :

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

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

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

9
ответ дан 23 November 2019 в 23:55
поделиться

Это - хороший совет. Вот то, что Brian Pepin должен сказать относительно вопроса:

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

8
ответ дан 23 November 2019 в 23:55
поделиться

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

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

MSDN:

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

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

Затем это означает, что все Ваши объекты должны быть неизменными, или метод 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 установлено на 'Политика', и свойство Name instance2 установлено на 'Piet'. Оба экземпляра возвращают другой хэш-код, и они также не равны. Теперь, предположите, что я меняю Имя instance2 'Политику', затем, согласно моему Равняется методу, оба экземпляра должны быть равными, и согласно одному из правил MSDN, они должны возвратить тот же хэш-код.
Однако это не может быть сделано, так как хэш-код instance2 изменится, и MSDN указывает, что это не позволяется.

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

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

9
ответ дан 23 November 2019 в 23:55
поделиться

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

5
ответ дан 23 November 2019 в 23:55
поделиться

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

, Если Ваш объект использует семантику значения, т.е. идентификационные данные объекта определяются его значениями (как Строка, Цвет, все структуры). Если идентификационные данные Вашего объекта независимы от всех своих значений, то Хэш-код определяется подмножеством его значений. Например, Ваша запись StackOverflow хранится в базе данных где-нибудь. Если Вы меняете свое имя или электронную почту, Ваша клиентская запись остается такой же, хотя некоторые значения изменились (в конечном счете, Вы обычно идентифицируетесь некоторым длинным идентификатором клиента #).

Так короче говоря:

семантика типа Значения - Хэш-код определяется семантикой Ссылочного типа значений - Хэш-код определяется некоторым идентификатором

, я предполагаю, что Вы читаете Доменный Управляемый Дизайн Eric Evans, где он входит в объекты по сравнению с типами значения (который является более или менее, что я попытался сделать выше), если это все еще не имеет смысла.

4
ответ дан 23 November 2019 в 23:55
поделиться

Посмотрите эту статью в блоге Марка Брукса:

VTO, RTO и GetHashCode() - о, Боже!

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

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

5
ответ дан 23 November 2019 в 23:55
поделиться

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

Но сначала о главном: Руководство, приведенное в вопросе, неверно.

Теперь о причинах - их две

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

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

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

Представьте два объекта, содержащих имя, где имя используется в методе equals: Одно и то же имя -> одно и то же. Создайте экземпляр A: Имя = Джо Создайте экземпляр B: Имя = Питер

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

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

Но именно такое поведение запрещено явным образом контрактом equals&hashcode.

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


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

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

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

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

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

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

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

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

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

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

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

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

.
120
ответ дан 23 November 2019 в 23:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: