Какова роль GetHashCode в IEqualityComparer <T> в .NET?

Я пытаюсь понять роль метода GetHashCode для интерфейса IEqualityComparer.

Следующий пример берется из MSDN:

using System;
using System.Collections.Generic;
class Example {
    static void Main() {
        try {

            BoxEqualityComparer boxEqC = new BoxEqualityComparer();

            Dictionary<Box, String> boxes = new Dictionary<Box,
                                                string>(boxEqC);

            Box redBox = new Box(4, 3, 4);
            Box blueBox = new Box(4, 3, 4);

            boxes.Add(redBox, "red");
            boxes.Add(blueBox, "blue");

            Console.WriteLine(redBox.GetHashCode());
            Console.WriteLine(blueBox.GetHashCode());
        }
        catch (ArgumentException argEx) {

            Console.WriteLine(argEx.Message);
        }
    }
}

public class Box {
    public Box(int h, int l, int w) {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; set; }
    public int Length { get; set; }
    public int Width { get; set; }
}

class BoxEqualityComparer : IEqualityComparer<Box> {

    public bool Equals(Box b1, Box b2) {
        if (b1.Height == b2.Height & b1.Length == b2.Length
                            & b1.Width == b2.Width) {
            return true;
        }
        else {
            return false;
        }
    }

    public int GetHashCode(Box bx) {
        int hCode = bx.Height ^ bx.Length ^ bx.Width;
        return hCode.GetHashCode();
    }
}

Не следует ли реализовать реализацию метода Equals для сравнения двух объектов Box? Именно там мы говорим структуре, что используется для сравнения объектов. Почему нужен GetHashCode?

Спасибо.

Лусиан

Ответ 1

Сначала немного фона...

Каждый объект в .NET имеет метод Equals и метод GetHashCode.

Метод Equals используется для сравнения одного объекта с другим объектом - для проверки эквивалентности двух объектов.

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

Словарь - это действительно классная структура данных, которая обрабатывает более высокий объем памяти в обмен на (более или менее) постоянные затраты для операций "Добавить/Удалить/Получить". Однако это плохой выбор для повторения. Внутри словарь содержит массив ведер, где значения могут быть сохранены. Когда вы добавляете ключ и значение в словарь, метод GetHashCode вызывается в ключе. Возвращенный hashcode используется для определения индекса ведра, в котором должна храниться пара ключей/значений.

Когда вы хотите получить доступ к значению, вы снова включаете ключ. Метод GetHashCode вызывается в ключе, и ведро, содержащее значение, находится.

Когда IEqualityComparer передается в конструктор словаря, вместо методов объектов Key используются методы IEqualityComparer.Equals и IEqualityComparer.GetHashCode.

Теперь, чтобы объяснить, почему оба метода необходимы, рассмотрите этот пример:

BoxEqualityComparer boxEqC = new BoxEqualityComparer(); 

Dictionary<Box, String> boxes = new Dictionary<Box, string>(boxEqC); 

Box redBox = new Box(100, 100, 25);
Box blueBox = new Box(1000, 1000, 25);

boxes.Add(redBox, "red"); 
boxes.Add(blueBox, "blue"); 

Используя метод BoxEqualityComparer.GetHashCode в вашем примере, оба этих поля имеют один и тот же хэш-код - 100 ^ 100 ^ 25 = 1000 ^ 1000 ^ 25 = 25 - хотя они явно не являются одним и тем же объектом. Причина, по которой они являются одним и тем же хэш-кодом в этом случае, заключается в том, что вы используете оператор ^ (побитовый исключающий-ИЛИ), поэтому 100 ^ 100 отменяет нулевое значение, равно 1000 1000. Когда два разных объекта имеют один и тот же ключ, мы называем это столкновением.

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

В примере, который вы опубликовали, два поля эквивалентны, поэтому метод Equals возвращает true. В этом случае словарь имеет два идентичных ключа, поэтому он генерирует исключение.

TL;DR

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

Надеюсь, что поможет

Ответ 2

GetHashCode используется в сочетании словарей и создает хэш для хранения в нем объектов. Вот хорошая статья, почему и как использовать IEqualtyComparer и GetHashCode http://dotnetperls.com/iequalitycomparer

Ответ 3

Хотя возможно, что Dictionary<TKey,TValue> имеет GetValue и аналогичные методы, вызовите Equals на каждый отдельный сохраненный ключ, чтобы убедиться, что он соответствует поисковому запросу, который будет очень медленным. Вместо этого, как и многие коллекции на основе хешей, он полагается на GetHashCode, чтобы быстро исключить большинство несогласованных значений из рассмотрения. Если при вызове GetHashCode на запрашиваемом элементе получается 42, а коллекция имеет 53 917 элементов, но вызов GetHashCode на 53,914 пунктов дал значение, отличное от 42, тогда только 3 элемента должны быть сопоставлены с теми, которые являются искал. Остальные 53 914 можно безопасно игнорировать.

Причина, по которой a GetHashCode включена в IEqualityComparer<T>, заключается в том, чтобы разрешить потребителю словаря рассматривать как равные объекты, которые обычно не рассматривают друг друга как равные. Наиболее распространенным примером может быть вызывающий, который хочет использовать строки в качестве ключей, но использует сравнения без учета регистра. Чтобы сделать эту работу эффективной, словарь должен иметь некоторую форму хэш-функции, которая даст то же значение для "Fox" и "FOX", но, надеюсь, даст что-то еще для "коробки" или "зебры". Поскольку метод GetHashCode, встроенный в String, не работает таким образом, словарь должен будет получить такой метод из другого места, а IEqualityComparer<T> является наиболее логичным местом, поскольку потребность в таком хэш-коде будет очень сильно связан с методом Equals, который считает "Fox" и "FOX" идентичными друг другу, но не "box" или "zebra".