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


142

Я намагаюся зрозуміти роль методу 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();
    }
}

Чи не має бути впровадження методу рівних достатньо для порівняння двох об'єктів Box? Саме тут ми повідомляємо основу, яке застосовується для порівняння об'єктів. Для чого потрібен GetHashCode?

Дякую.

Лучан


Прочитайте: en.wikipedia.org/wiki/Hash_table, тоді подивіться, чи краще ви розумієте мету GetHashCode.
витрачається

1
Дивіться цю чудову відповідь: stackoverflow.com/a/3719802/136967
Михайло

Відповіді:


201

Спочатку трішки фону ...

Кожен об’єкт у .NET має метод Equals та метод GetHashCode.

Метод рівняння використовується для порівняння одного об'єкта з іншим об'єктом - щоб побачити, чи два об'єкти рівнозначні.

Метод GetHashCode генерує 32-бітове ціле представлення об'єкта. Оскільки немає обмеження в кількості інформації, яку може містити об'єкт, певні хеш-коди діляться кількома об'єктами - тому хеш-код не обов'язково є унікальним.

Словник - це дійсно крута структура даних, яка торгує більш високим слідом пам’яті взамін на (більш-менш) постійні витрати на операції «Додати / видалити / отримати». Це поганий вибір для повторення, хоча. Внутрішньо словник містить масив відро, де можна зберігати значення. Коли ви додаєте ключ і значення до словника, на клавіші викликається метод GetHashCode. Повернений хеш-код використовується для визначення індексу відра, в якому повинна зберігатися пара ключ / значення.

Коли ви хочете отримати доступ до Значення, ви знову переходите до ключа. Метод GetHashCode викликається на ключі, і розташоване відро із значенням.

Коли IEqualityComparer передається в конструктор словника, замість методів на ключових об'єктах використовуються методи 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 викликає наш ключ, щоб знайти відро. Оскільки у відрі є більше одного значення, словник здійснює повторне виконання всіх пар ключів / значень у відрі, викликаючи метод рівних на ключах, щоб знайти правильне.

У прикладі, який ви розмістили, два поля є рівнозначними, тому метод рівних повертає істину. У цьому випадку словник має два однакові ключі, тому він кидає виняток.

TLDR

Отже, підсумовуючи, метод GetHashCode використовується для генерування адреси, де зберігається об'єкт. Тож словник не повинен його шукати. Він просто обчислює хеш-код і переходить до цього місця. Метод рівності - це кращий тест рівності, але його не можна використовувати для відображення об'єкта в адресний простір.


4
Для тих, хто цікавиться, що таке ^ -оператор, це оператор ексклюзивного АБО-АБО, див. Msdn.microsoft.com/en-us/library/zkacc7k1.aspx .
Р. Шреурс

2
Просто для чіткого вказівки на це: ( msdn.microsoft.com/en-us/library/ms132155.aspx ) Примітки для виконавців Реалізації потрібно, щоб переконатися, що якщо метод Equals повертає значення true для двох об'єктів x і y, то значення повертається методом GetHashCode для x повинно дорівнювати значення, повернене для y.
Дієго Френер

2
@DiegoFrehner - Ти абсолютно прав. Інша річ, яка може відвернути людей - це те, що значення методу GetHashCode не має змінюватися, якщо об'єкт модифікований. Отже, поля в об'єкті, від яких залежить GetHashCode, повинні читатися лише (незмінні). Там є пояснення: stackoverflow.com/a/4868940/469701
sheikhjabootie

1
@Acentric: Хеш-код об'єкта не повинен змінюватися, якщо він не змінюється таким чином, що впливає на рівність. Якщо клас можна мутувати таким чином, що впливає на рівність, код повинен уникати зберігання у словнику будь-якого екземпляра, який може бути підданий дії коду, який мітував би його, поки він знаходиться у словнику. Якщо код, який зберігає об'єкт, дотримується цього правила, може бути корисним хеш-код, який відображає стан, що змінюється. Це дуже погано.
supercat

3
@Acentric: Навіть за винятком використання хеш-коду для адреси хеш-таблиць, основна ідея хеш-коду полягає в тому, що знання, що два об'єкти мають різні хеш-коди, означає, що вони нерівні і не потрібно їх порівнювати. Як наслідок, знання того, що хеш-коди багатьох об'єктів не відповідають хеш-коду заданого об'єкта, означає, що жоден з них не дорівнює об’єкту. Використання хеш-коду для адреси - це в основному спосіб ігнорування об'єктів, які мають різні хеш-коди.
supercat

9

GetHashCode використовується в колекціях словника, і він створює хеш для зберігання в ньому об'єктів. Ось приємна стаття, чому і як користуватися IEqualtyComparer та GetHashCode http://dotnetperls.com/iequalitycomparer


4
Докладніше: Якщо вам потрібно порівнювати рівних, було б enouf, але коли вам потрібно отримати елемент зі словника, простіше це зробити хешем, а не використанням рівних .
Еш

5

Хоча можливо, Dictionary<TKey,TValue>щоб його GetValueі подібні методи викликали Equalsкожен збережений ключ, щоб побачити, чи відповідає він тому, що шукається, це буде дуже повільно. Натомість, як і багато колекцій на основі хешу, вона покладається на GetHashCodeшвидке виключення з розгляду більшості невідповідних значень. Якщо дзвінок GetHashCodeна предмет, який шукається, дає 42, а колекція - 53 917 предметів, а при виклику GetHashCode53 914 предметів дасть значення, відмінне від 42, тоді лише 3 предмети доведеться порівняти з тими, що шукаються. Інші 53 914 можна сміливо ігнорувати.

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


Правильна та суттєва відповідь на питання! GetHashCode () повинен доповнювати рівняння () для відповідних об'єктів.
Sumith

@Sumith: Багато дискусій хешингу говорять про відра, але я думаю, що корисніше думати про виключення. Якщо порівняння дороге, хешування може запропонувати переваги навіть при використанні колекцій, які не організовані у відрі.
Supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.