Vai trò của GetHashCode trong IEqualityComparer <T> trong .NET là gì?


142

Tôi đang cố gắng hiểu vai trò của phương thức GetHashCode của giao diện IEqualityComparer.

Ví dụ sau được lấy từ 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();
    }
}

Không nên triển khai phương thức Equals đủ để so sánh hai đối tượng Box? Đó là nơi chúng ta nói với khung quy tắc được sử dụng để so sánh các đối tượng. Tại sao GetHashCode cần thiết?

Cảm ơn.

Lucian


Hãy đọc: en.wikipedia.org/wiki/Hash_table sau đó xem bạn có hiểu rõ hơn về mục đích của GetHashCode không.
tiêu

1
Xem câu trả lời tuyệt vời này: stackoverflow.com/a/3719802/136967
Mikhail

Câu trả lời:


200

Một chút nền tảng đầu tiên ...

Mọi đối tượng trong .NET đều có phương thức Equals và phương thức GetHashCode.

Phương thức Equals được sử dụng để so sánh một đối tượng với một đối tượng khác - để xem liệu hai đối tượng có tương đương nhau không.

Phương thức GetHashCode tạo ra biểu diễn số nguyên 32 bit của đối tượng. Vì không có giới hạn về số lượng thông tin mà một đối tượng có thể chứa, một số mã băm nhất định được chia sẻ bởi nhiều đối tượng - vì vậy mã băm không nhất thiết phải là duy nhất.

Từ điển là một cấu trúc dữ liệu thực sự tuyệt vời, giao dịch một bộ nhớ cao hơn để đổi lấy (nhiều hơn hoặc ít hơn) chi phí không đổi cho các hoạt động Thêm / Xóa / Nhận. Đó là một lựa chọn kém cho việc lặp đi lặp lại mặc dù. Trong nội bộ, một từ điển chứa một loạt các thùng, nơi các giá trị có thể được lưu trữ. Khi bạn thêm Khóa và Giá trị vào từ điển, phương thức GetHashCode được gọi trên Khóa. Mã băm được trả về được sử dụng để xác định chỉ mục của nhóm trong đó cặp Khóa / Giá trị sẽ được lưu trữ.

Khi bạn muốn truy cập Giá trị, bạn chuyển lại Khóa. Phương thức GetHashCode được gọi trên Khóa và thùng chứa Giá trị được đặt.

Khi một IEqualityComparer được truyền vào hàm tạo của từ điển, các phương thức IEqualityComparer.Equals và IEqualityComparer.GetHashCode được sử dụng thay cho các phương thức trên các đối tượng Key.

Bây giờ để giải thích tại sao cả hai phương pháp đều cần thiết, hãy xem xét ví dụ này:

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"); 

Sử dụng phương thức BoxEqualityComparer.GetHashCode trong ví dụ của bạn, cả hai hộp này đều có cùng mã băm - 100 ^ 100 ^ 25 = 1000 ^ 1000 ^ 25 = 25 - mặc dù rõ ràng chúng không phải là cùng một đối tượng. Lý do chúng là cùng một mã băm trong trường hợp này là vì bạn đang sử dụng toán tử ^ (bitwise độc ​​quyền-OR) để 100 ^ 100 hủy bỏ không để lại, cũng như 1000 ^ 1000. Khi hai đối tượng khác nhau có cùng khóa, chúng ta gọi đó là xung đột.

Khi chúng tôi thêm hai cặp Khóa / Giá trị có cùng mã băm vào từ điển, cả hai đều được lưu trữ trong cùng một nhóm. Vì vậy, khi chúng tôi muốn truy xuất một Giá trị, phương thức GetHashCode được gọi trên Khóa của chúng tôi để xác định vị trí nhóm. Vì có nhiều hơn một giá trị trong nhóm, từ điển lặp lại trên tất cả các cặp Khóa / Giá trị trong nhóm gọi phương thức Equals trên Khóa để tìm đúng.

Trong ví dụ mà bạn đã đăng, hai hộp tương đương nhau, vì vậy phương thức Equals trả về true. Trong trường hợp này, từ điển có hai Khóa giống nhau, vì vậy nó ném một ngoại lệ.

TLD

Vì vậy, tóm lại, phương thức GetHashCode được sử dụng để tạo một địa chỉ nơi đối tượng được lưu trữ. Vì vậy, một từ điển không phải tìm kiếm nó. Nó chỉ tính toán mã băm và nhảy đến vị trí đó. Phương thức Equals là một phép thử tốt hơn cho đẳng thức, nhưng không thể được sử dụng để ánh xạ một đối tượng vào một không gian địa chỉ.


4
Đối với những người, tự hỏi ^ -operator là gì, đây là toán tử OR-bit độc quyền, xem msdn.microsoft.com/en-us/l Library / zkacc7k1.aspx .
R. Schreurs

2
Chỉ cần chỉ này một cách rõ ràng ra: ( msdn.microsoft.com/en-us/library/ms132155.aspx ) Thuyết minh Implementers Triển khai được yêu cầu để đảm bảo rằng nếu Equals trở về phương pháp đúng đối với hai đối tượng x và y, sau đó giá trị trả về bởi phương thức GetHashCode cho x phải bằng giá trị được trả về cho y.
Diego Frehner

2
@DiegoFrehner - Bạn hoàn toàn đúng. Một điều nữa có thể khiến mọi người gặp khó khăn là giá trị của phương thức GetHashCode không nên thay đổi nếu đối tượng được sửa đổi. Vì vậy, các trường trong đối tượng mà GetHashCode phụ thuộc vào nên chỉ đọc (không thay đổi). Có một lời giải thích ở đây: stackoverflow.com/a/4868940/469701
sheikhjabootie

1
@Acentric: Mã băm của một đối tượng không nên thay đổi trừ khi nó bị biến đổi theo kiểu ảnh hưởng đến sự bình đẳng. Nếu một lớp có thể bị đột biến theo kiểu như vậy để ảnh hưởng đến sự bình đẳng, mã nên tránh lưu trữ trong từ điển bất kỳ trường hợp nào có thể tiếp xúc với mã sẽ làm biến đổi nó trong khi đó trong từ điển. Nếu mã lưu trữ đối tượng tuân theo quy tắc đó, có mã băm phản ánh trạng thái có thể thay đổi có thể hữu ích. Thật tệ khi .NET không phân biệt tốt hơn sự bình đẳng và tương đương của nhà nước, vì cả hai đều là những khái niệm hữu ích.
supercat

3
@Acentric: Thậm chí ngoài việc sử dụng mã băm để đánh địa chỉ bảng băm, ý tưởng cơ bản đằng sau mã băm là kiến ​​thức rằng hai đối tượng có mã băm khác nhau ngụ ý rằng chúng không bằng nhau và không cần so sánh chúng. Như một hệ quả tất yếu, kiến ​​thức rằng mã băm của nhiều đối tượng không khớp với mã băm của một đối tượng nhất định ngụ ý rằng không ai trong số chúng bằng với đối tượng. Sử dụng mã băm để đánh địa chỉ về cơ bản là một cách bỏ qua các đối tượng có mã băm khác nhau.
supercat

9

GetHashCode được sử dụng trong các bộ sưu tập Từ điển và nó tạo ra hàm băm để lưu trữ các đối tượng trong đó. Đây là một bài viết hay tại sao và làm thế nào để sử dụng IEqualtyComparerGetHashCode http://dotnetperls.com/iequalitycomparer


4
Hơn: Nếu bạn cần phải so sánh Equals sẽ enouf, nhưng khi bạn cần phải nhận được phần tử từ điển nó dễ dàng hơn để làm điều này bằng cách băm, không bằng cách sử dụng Equals .
Tro

5

Mặc dù có thể có một phương thức tương tự và tương tự Dictionary<TKey,TValue>của nó GetValuegọi Equalstrên mỗi khóa được lưu trữ duy nhất để xem liệu nó có khớp với khóa đang tìm kiếm hay không, điều đó sẽ rất chậm. Thay vào đó, giống như nhiều bộ sưu tập dựa trên hàm băm, nó dựa vào GetHashCodeđể nhanh chóng loại trừ hầu hết các giá trị không phù hợp khỏi xem xét. Nếu gọi GetHashCodemột mặt hàng đang được tìm kiếm mang lại 42 và một bộ sưu tập có 53.917 mặt hàng, nhưng gọiGetHashCode 53.914 mặt hàng mang lại giá trị khác 42, thì chỉ có 3 mặt hàng sẽ được so sánh với các mặt hàng được tìm kiếm. 53.914 khác có thể được bỏ qua một cách an toàn.

Lý do a GetHashCodeđược bao gồm trong một IEqualityComparer<T>là để cho phép khả năng người tiêu dùng của từ điển có thể muốn coi là các đối tượng bình đẳng thường không coi nhau là như nhau. Ví dụ phổ biến nhất sẽ là một người gọi muốn sử dụng các chuỗi làm khóa nhưng sử dụng các so sánh không phân biệt chữ hoa chữ thường. Để làm cho nó hoạt động hiệu quả, từ điển sẽ cần có một số dạng hàm băm sẽ mang lại giá trị tương tự cho "Fox" và "FOX", nhưng hy vọng sẽ mang lại một cái gì đó khác cho "hộp" hoặc "ngựa vằn". Vì GetHashCodephương thức được tích hợp Stringkhông hoạt động theo cách đó, từ điển sẽ cần lấy phương thức đó từ nơi khác,IEqualityComparer<T>Equals phương pháp coi "Fox" và "FOX" giống hệt nhau, nhưng không phải là "hộp" hoặc "ngựa vằn".


Câu trả lời đúng và chính xác cho câu hỏi! GetHashCode () phải bổ sung Equals () cho các đối tượng trong câu hỏi.
Sumith

@Sumith: Nhiều cuộc thảo luận về băm nói về xô, nhưng tôi nghĩ sẽ hữu ích hơn khi nghĩ đến việc loại trừ. Nếu so sánh là đắt tiền, băm có thể mang lại lợi ích ngay cả khi sử dụng các bộ sưu tập không được tổ chức thành xô.
supercat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.