Переопределение GetHashCode и Equals в C#

Я проводил собеседование с кандидатом с опытом работы более 6 лет и спросил:

Что такое GetHashCode в C# .net. и где он используется?

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

Тот факт, что метод GetHashCode возвращает адрес объекта в управляемой куче, является мифом. Это не может быть правдой из-за его непостоянства. Сборщик мусора при уплотнении бедра сдвигает объекты и тем самым меняет все их адреса.

Начнем с вопроса Зачем нам это нужно?

Метод GetHashCode предоставляет этот хэш-код для алгоритмов, которым требуется быстрая проверка равенства объектов. Хэш-код — это числовое значение, которое используется для вставки и идентификации объекта в коллекции на основе хэша, такой как класс Dictionary, класс Hashtable или тип, производный от класса DictionaryBase.

Два одинаковых объекта возвращают одинаковые хеш-коды. Однако обратное неверно: одинаковые хеш-коды не означают равенства объектов, поскольку разные (неравные) объекты могут иметь одинаковые хеш-коды.

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

class Program
    {
        static void Main(string[] args)
        {
            var obj1 = new AllowedItem("A-Key", "A-Value", true);
            var obj2 = new AllowedItem("A-Key", "A-Value", true);

            var dic = new Dictionary<AllowedItem, string>();
            dic.Add(obj1, "obj1");
            dic.Add(obj2, "obj2");
        }
    }

    public class AllowedItem
    {
        public string Name { get; private set; }
        public string Value { get; private set; }
        public bool IsAllowed { get; private set; }

        public AllowedItem(string name, string value, bool isAllowed)
        {
            Name = name;
            Value = value;
            IsAllowed = isAllowed;
        }

        public override bool Equals(object obj)
        {
            if (obj is AllowedItem other)
            {
                if (Name == other.Name && Value == other.Value && IsAllowed == other.IsAllowed)
                    return true;
            }
            return false;           
        }

        public override int GetHashCode()
        {
            return Name.GetHashCode() ^
                Value.GetHashCode() ^
                IsAllowed.GetHashCode();
        }
    }

Мы пытаемся вставить 2 одинаковых объекта в качестве ключа в словарь. Здесь это вызовет исключение ниже, когда в Disctionay вставляется дубликат ключа.

System.ArgumentException: «Элемент с таким же ключом уже добавлен. Ключ: ConsoleApp2.AllowedItem’

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

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

public interface IEqualityComparer<in T>
    {
        bool Equals(T x, T y);
        int GetHashCode(T obj);
    }

Теперь, когда мы знаем, почему мы используем GetHashCode, давайте ответим на еще один важный вопрос.

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

int x = 16;
var intHash = x.GetHashCode(); //Result: 16
bool y = true;
var boolHash = y.GetHashCode(); //Result:1

Ссылочный тип немного сложен. Начиная с .NET 2.0 алгоритм хеширования изменен. Теперь он использует управляемый идентификатор потока, в котором выполняется метод, и метод выглядит так:

inline DWORD GetNewHashCode()
{
      // Every thread has its own generator for hash codes so that we won't get into a situation
      // where two threads consistently give out the same hash codes.
      // Choice of multiplier guarantees period of 2**32 - see Knuth Vol 2 p16 (3.2.1.2 Theorem A)
     DWORD multiplier = m_ThreadId*4 + 5;
     m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1;
     return m_dwHashCodeSeed;
}

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

При первом вызове метода GetHashCode CLR оценивает хэш-код и помещает его в поле SyncBlockIndex объекта. Если SyncBlock связан с объектом, т. е. используется поле SyncBlockIndex, CLR записывает хэш-код в сам SyncBlock. Как только SyncBlock освобождается, CLR копирует хэш-код из его тела в заголовок объекта SyncBlockIndex. Это все.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *