Учитывая следующий класс
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
Я переопределил Equals
метод, потому что Foo
представляет строку для Foo
таблицы s. Какой способ переопределения является предпочтительным GetHashCode
?
Почему важно переопределить GetHashCode
?
c#
overriding
hashcode
Дэвид Басараб
источник
источник
Ответы:
Да, важно, если ваш элемент будет использоваться в качестве ключа в словаре или
HashSet<T>
и т. Д., Поскольку он используется (при отсутствии пользовательскогоIEqualityComparer<T>
) для группировки элементов в сегменты. Если хеш-код для двух элементов не совпадает, они никогда не могут считаться равными ( равно никогда не будет вызываться Equals ).Метод GetHashCode () должен отражать
Equals
логику; Правила таковы:Equals(...) == true
), то они должны возвращать одно и то же значение дляGetHashCode()
GetHashCode()
равны, им не обязательно быть одинаковыми; это столкновение, иEquals
будет вызвано, чтобы увидеть, является ли это реальным равенством или нет.В этом случае это выглядит как "
return FooId;
" подходящаяGetHashCode()
реализация. Если вы тестируете несколько свойств, обычно их объединяют с использованием кода, подобного приведенному ниже, для уменьшения диагональных коллизий (т. Е.new Foo(3,5)
С использованием другого хэш-кодаnew Foo(5,3)
):О - для удобства, вы можете также рассмотреть вопрос о предоставлении
==
и!=
операторы при переопределенииEquals
иGetHashCode
.Демонстрация того, что происходит, когда вы ошибаетесь, здесь .
источник
На самом деле это очень сложно реализовать
GetHashCode()
правильно, потому что, в дополнение к уже упомянутым Марком правилам, хеш-код не должен изменяться в течение всего времени существования объекта. Поэтому поля, которые используются для вычисления хеш-кода, должны быть неизменными.Наконец-то я нашел решение этой проблемы, когда работал с NHibernate. Мой подход заключается в том, чтобы вычислить хэш-код из идентификатора объекта. Идентификатор может быть установлен только через конструктор, поэтому, если вы хотите изменить идентификатор, что очень маловероятно, вам нужно создать новый объект, который имеет новый идентификатор и, следовательно, новый хэш-код. Этот подход лучше всего работает с GUID, потому что вы можете предоставить конструктор без параметров, который генерирует случайный идентификатор.
источник
Переопределяя Equals, вы в основном утверждаете, что вы - тот, кто лучше знает, как сравнивать два экземпляра данного типа, поэтому вы, вероятно, будете лучшим кандидатом для предоставления лучшего хэш-кода.
Это пример того, как ReSharper пишет для вас функцию GetHashCode ():
Как вы можете видеть, он просто пытается угадать хороший хеш-код, основанный на всех полях в классе, но, поскольку вы знаете домен или диапазон значений вашего объекта, вы все равно можете предоставить лучший.
источник
0 ^ a = a
, так0 ^ m_someVar1 = m_someVar1
. Он мог бы также установить начальное значениеresult
вm_someVar1
.Пожалуйста, не забудьте проверить параметр obj
null
при переопределенииEquals()
. А также сравните тип.Причина этого заключается в следующем:
Equals
должен возвращать false при сравнении сnull
. Смотрите также http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxисточник
obj
действительно равенthis
независимо от того, как вызывался Equals () базового класса.fooItem
вверх и проверка его на нулевое значение будет работать лучше в случае нулевого или неправильного типа.obj as Foo
будет недействительным.Как насчет:
источник
string.Format
. Еще один вызывающий, который я видел, этоnew { prop1, prop2, prop3 }.GetHashCode()
. Не могу прокомментировать, хотя какой из них будет медленнее между этими двумя. Не злоупотребляйте инструментами.{ prop1="_X", prop2="Y", prop3="Z" }
и{ prop1="", prop2="X_Y", prop3="Z_" }
. Вы, вероятно, не хотите этого.У нас есть две проблемы, чтобы справиться.
Вы не можете предоставить разумное,
GetHashCode()
если любое поле в объекте может быть изменено. Также часто объект НИКОГДА не будет использоваться в коллекции, от которой зависитGetHashCode()
. Таким образом, стоимость внедренияGetHashCode()
часто не стоит, или это невозможно.Если кто-то помещает ваш объект в коллекцию, которая вызывает,
GetHashCode()
и вы перезаписали,Equals()
не заставляяGetHashCode()
себя вести себя правильно, этот человек может потратить дни на то, чтобы отследить проблему.Поэтому по умолчанию я делаю.
источник
GetHashCode
функцию, чтобы любые два равных объекта возвращали один и тот же хэш-код;return 24601;
иreturn 8675309;
оба будут действительными реализациямиGetHashCode
. ПроизводительностьDictionary
будет приличной только тогда, когда количество предметов невелико, и будет очень плохой, если количество предметов увеличится, но в любом случае она будет работать правильно.Это связано с тем, что инфраструктура требует, чтобы два одинаковых объекта имели одинаковый хэш-код. Если вы переопределяете метод equals, чтобы выполнить специальное сравнение двух объектов, и эти два метода считаются одинаковыми, то хэш-код двух объектов также должен быть одинаковым. (Словари и Hashtables опираются на этот принцип).
источник
Просто чтобы добавить ответы выше:
Если вы не переопределяете Equals, то поведение по умолчанию состоит в том, что ссылки на объекты сравниваются. То же самое относится и к хэш-коду - имплементация по умолчанию обычно основана на адресе памяти ссылки. Поскольку вы переопределили Equals, это означает, что правильное поведение - сравнивать то, что вы реализовали в Equals, а не в ссылках, поэтому вы должны сделать то же самое для хэш-кода.
Клиенты вашего класса ожидают, что хеш-код будет иметь аналогичную логику с методом equals, например, методы linq, которые используют IEqualityComparer, сначала сравнивают хеш-коды и только если они равны, они будут сравнивать метод Equals (), который может быть более дорогим для запуска, если мы не реализовали хеш-код, равный объект, вероятно, будет иметь разные хеш-коды (потому что они имеют разные адреса памяти) и будет определен неправильно как не равный (Equals () даже не попадет).
Кроме того, за исключением проблемы, заключающейся в том, что вы не сможете найти свой объект, если будете использовать его в словаре (поскольку он был вставлен одним хеш-кодом, и при его поиске хеш-код по умолчанию, вероятно, будет другим, и снова Equals () даже не будет вызван, как объясняет Марк Гравелл в своем ответе, вы также вводите нарушение словаря или концепции хэш-набора, которая не должна позволять идентичные ключи - вы уже объявили, что эти объекты по сути одинаковы, когда вы переопределяете Equals, поэтому не требуется, чтобы они оба были разными ключами в структуре данных, в которой предполагается, что они имеют уникальный ключ, но поскольку они имеют разные хэш-коды, «один и тот же» ключ будет вставлен как другой.
источник
Хеш-код используется для коллекций на основе хеша, таких как Dictionary, Hashtable, HashSet и т. Д. Целью этого кода является очень быстрая предварительная сортировка определенного объекта путем помещения его в определенную группу (сегмент). Эта предварительная сортировка чрезвычайно помогает в поиске этого объекта, когда вам нужно извлечь его из хэш-коллекции, потому что код должен искать ваш объект только в одном сегменте, а не во всех объектах, которые он содержит. Чем лучше распределение хеш-кодов (лучшая уникальность), тем быстрее поиск. В идеальной ситуации, когда каждый объект имеет уникальный хеш-код, его нахождение - это операция O (1). В большинстве случаев оно приближается к O (1).
источник
Это не обязательно важно; это зависит от размера ваших коллекций и ваших требований к производительности, а также от того, будет ли ваш класс использоваться в библиотеке, где вы, возможно, не знаете требований к производительности. Я часто знаю, что размеры моей коллекции не очень велики, и мое время более ценно, чем несколько микросекунд производительности, получаемой за счет создания идеального хеш-кода; поэтому (чтобы избавиться от надоедливого предупреждения от компилятора) я просто использую:
(Конечно, я мог бы использовать #pragma, чтобы отключить предупреждение, но я предпочитаю этот способ.)
Когда вы находитесь в положении, когда вам действительно нужна производительность, то, конечно, применимы все проблемы, упомянутые здесь другими. Наиболее важно - в противном случае вы получите неправильные результаты при извлечении элементов из хеш-набора или словаря: хеш-код не должен изменяться в зависимости от времени жизни объекта (точнее, во время, когда требуется хеш-код, например, при ключ в словаре): например, следующее неверно, поскольку Value является общедоступным и поэтому может быть изменено внешне для класса в течение времени жизни экземпляра, поэтому вы не должны использовать его в качестве основы для хеш-кода:
С другой стороны, если значение не может быть изменено, можно использовать:
источник
Вы всегда должны гарантировать, что если два объекта равны, как определено Equals (), они должны возвращать один и тот же хеш-код. Как утверждают некоторые другие комментарии, в теории это не является обязательным, если объект никогда не будет использоваться в контейнере на основе хеша, таком как HashSet или Dictionary. Я бы посоветовал вам всегда следовать этому правилу. Причина в том, что для кого-то слишком легко изменить коллекцию с одного типа на другой с хорошим намерением реально повысить производительность или просто лучше передать семантику кода.
Например, предположим, что мы храним некоторые объекты в списке. Некоторое время спустя кто-то на самом деле понимает, что HashSet - гораздо лучшая альтернатива, например, из-за лучших характеристик поиска. Это когда мы можем попасть в беду. Для внутреннего использования List будет использовать для сравнения тип равенства по умолчанию, что в вашем случае означает Equals, а HashSet использует GetHashCode (). Если они ведут себя по-разному, то и ваша программа тоже. И имейте в виду, что такие проблемы не легко устранить.
Я суммировал это поведение с некоторыми другими подводными камнями GetHashCode () в блоге, где вы можете найти дополнительные примеры и объяснения.
источник
Как
.NET 4.7
предпочтительный способ переопределенияGetHashCode()
показан ниже. Если вы нацелены на более старые версии .NET, включите пакет nuget System.ValueTuple .С точки зрения производительности этот метод превзойдет большинство реализаций составного хеш-кода. ValueTuple является
struct
так что не будет никакого мусора, и основной алгоритм так быстро , как он получает.источник
Насколько я понимаю, оригинальный GetHashCode () возвращает адрес памяти объекта, поэтому его необходимо переопределить, если вы хотите сравнить два разных объекта.
РЕДАКТИРОВАНИЕ: Это было неправильно, оригинальный метод GetHashCode () не может гарантировать равенство 2 значений. Хотя равные объекты возвращают один и тот же хэш-код.
источник
Ниже использование рефлексии кажется мне лучшим вариантом, учитывая открытые свойства, так как при этом вам не нужно беспокоиться о добавлении / удалении свойств (хотя это не так часто встречается). Я также обнаружил, что это работает лучше (по сравнению с секундомером Diagonistics).
источник