GetHashCode, Equals и их друзья
- Алик Ким
- 23 дек. 2023 г.
- 3 мин. чтения
Обновлено: 25 дек. 2023 г.
вот уже что-то в районе 10 лет я в .net - есть ощущение,пришло время изучить "Equals"
(может, кризис среднего возраста?)
нет, ну на самом деле какие то вещи я уже знал :) но системы в голове не было. а вот GetHashCode - это прям вообще было что-то, чего я избегал :)
и реально вот только что жизнь впервые приперла к стенке: понадобилось прописать проверку на равенство двух объектов :)
в целом, все по своим местам расставила эта статейка: https://sasan-salem.medium.com/mystery-of-equality-in-c-iequatable-t-iequalitycomparer-t-icomparable-t-icomparer-t-ab98bd2fe541
по GetHashCode - изучил документацию майкрософт на эту тему: https://learn.microsoft.com/en-us/dotnet/api/system.object.gethashcode
итак, вкратце, что выяснил:
Equals - ну, старый добрый Equals, призванный определять, равны ли два объекта.
GetHashCode - метод для получения целочисленного хешкода объекта. этот хешкод потом используется в некоторых коллекциях данных.
его прикол - в том, что 1) он вычисляется очень быстро (по сравнению с Equals) 2) если хеш-коды двух объектов одинаковы - это НЕ означает, что объекты одинаковы. но если они разные - это ГАРАНТИРУЕТ, что объекты разные.
думаю (и в статье такое же мнение высказывается), оптимально при массовых сравнениях использовать сначала GetHashCode (на которых выясняется неравенство, скажем, 90% объектов), а уже при их равенстве - использовать Equals.
хотя, c другой стороны, в документации по GetHashCode прямо запрещается использовать GetHashCode для сравнения объектов. можно предположить, что запретили на всякий случай, чтоб не использовали без учета описанного выше нюанса.
ну и вот, изза этой связи логики Equals и GetHashCode ИДЕ ругаются, когда ты переопределяешь Equals, но не переопределяешь GetHashCode.
Думаю, в общем случае достаточно в GetHashCode вернуть хеш какого-нибудь ключевого поля объекта или группы объектов, которые объект более-менее идентифицируют.
ИД, если есть. или ФИО + дату рождения, например.
я так понимаю, если этот метод неправильно запилишь - это отразится только на быстродействии, но не на логике приложения. лишь бы соблюдалось вон то правило соответствия в плане логики методу Equals
если ключевых полей несколько - их можно комбинировать тупо через XOR (^).
в целях оптимизации в описании GetHashCode предлагаются какие-то побитовые сдвиги - я не стал в этом разбираться, наверное, этим нужно будет отдельно заморачиваться, когда/если придет нужда.
хеши (не имеет смысла) нельзя персистить, они могут меняться в зависимости от среды.
еще нюанс:
Хеш не должен меняться в процессе жизни объекта внутри использующей хеш коллекции. например, если у объекта изменится поле, от которого зависит хеш - коллекция начнет глючить (обхект потеряется, например).
по умолчанию и Equals, и GetHashCode для ссылочных типов базируются на адресе объекта в памяти (то есть, Equals будет true, если это реально один и тот же объект).
для структур там свои приколы. там GetHashCode (ну и , видимо, Equals) делается через рефлекшен - наверное, используются все члены. рекомендуется переопределить GetHashCode для ускорения работы
у records , кажется, по умолчанию сравнение работает тоже почленно (как у структур)
вооть.
но кроме этих двух довольно известных ребят есть еще целый зоопарк, про который я как то системно и не слышал:
IEquatable, IEqualityComparer, IComparable и IComparer.
так то глаза разбегаются.
но в итоге все выглядит достаточно логично:
IEquatable<T> - это интерфейс, содержащий типизирвоаннуб/генериковую версию Equals.
Я так понимаю, генериковые коллекции пользуются этим интерфейсом (а не Equals), если он реализован.
преимущество ее - (кроме, собственно, строгой типизации) вот в чем: наш старый добрый Equals настолько стар, что не строготипизирован. следовательно, внутри него нужно в первую очередь проверять тип второго объекта на равенство типу этого объекта. вот этого шага позволяет избежать реализация этого интерфейса - класс заведомо совпадает. при массовых сравнениях - может сыграть свою роль.
так что в идеале:
реализуем IEquatable, потом реализуем нетипизированный Equals так: проверяем соответствие типов и вызываем Equatable
IEqualityComparer - просто способ использовать альтернативные правила сравнения, если встроенные в класс нас не устраивают. интерфейс содержит как Equals, так и GetHashCode, естественно.
все операции с коллекциями, где задейсвован Equals/GetHashCode, имеют перегрузки, принимающие IEqualityComparer
IComparable - для сортировки объектов. Equals у нас возвращает true/false. а этот - больше/меньше/равно (целые числа -1,0,1)
если класс реализует этот интерфейс - в IEquatable.Equals имеет смысле просто вызывать IComparable
IComparer<T> - внешний сравнивальщик (как IEqualityComparer) - если реализация IComparable не устраивает или отсутствует
コメント