.NET의 IEqualityComparer <T>에서 GetHashCode의 역할은 무엇입니까?


142

IEqualityComparer 인터페이스의 GetHashCode 메서드 역할을 이해하려고합니다.

다음 예제는 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가 필요한 이유는 무엇입니까?

감사.

루시안


en.wikipedia.org/wiki/Hash_table 을 읽고 GetHashCode의 목적을 더 잘 이해하고 있는지 확인하십시오.
쓰셨

1
이 위대한 답변을보십시오 : stackoverflow.com/a/3719802/136967
Mikhail

답변:


201

먼저 약간의 배경 ...

.NET의 모든 개체에는 Equals 메서드와 GetHashCode 메서드가 있습니다.

Equals 메서드는 한 개체를 다른 개체와 비교하는 데 사용됩니다. 두 개체가 같은지 확인합니다.

GetHashCode 메서드는 개체의 32 비트 정수 표현을 생성합니다. 개체에 포함 할 수있는 정보의 양에는 제한이 없으므로 특정 해시 코드는 여러 개체에서 공유되므로 해시 코드가 반드시 고유하지는 않습니다.

딕셔너리는 추가 / 제거 / 가져 오기 작업에 대한 일정한 비용을 대가로 더 높은 메모리 풋 프린트를 거래하는 정말 멋진 데이터 구조입니다. 그래도 반복하기에는 좋지 않은 선택입니다. 내부적으로 사전에는 값을 저장할 수있는 버킷 배열이 포함되어 있습니다. 키와 값을 사전에 추가하면 키에서 GetHashCode 메서드가 호출됩니다. 반환 된 해시 코드는 키 / 값 쌍을 저장해야하는 버킷의 인덱스를 결정하는 데 사용됩니다.

값에 액세스하려면 키를 다시 전달하십시오. GetHashCode 메서드는 Key에서 호출되며 Value가 포함 된 버킷이 있습니다.

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)를 갖지만 분명히 동일한 객체는 아닙니다. 이 경우에 동일한 해시 코드 인 이유는 ^ (비트 배타적 OR) 연산자를 사용하고 있기 때문에 100 ^ 100은 1000 ^ 1000과 마찬가지로 0을 남기지 않고 취소하기 때문입니다. 서로 다른 두 객체에 동일한 키가 있으면 충돌이라고합니다.

동일한 해시 코드를 가진 두 개의 키 / 값 쌍을 사전에 추가하면 둘 다 동일한 버킷에 저장됩니다. 따라서 Value를 검색하려면 키에서 GetHashCode 메서드가 호출되어 버킷을 찾습니다. 버킷에 둘 이상의 값이 있으므로 사전에서 버킷의 모든 키 / 값 쌍을 반복하여 키에서 Equals 메서드를 호출하여 올바른 값을 찾습니다.

게시 한 예에서 두 상자는 동일하므로 Equals 메서드는 true를 반환합니다. 이 경우 사전에는 두 개의 동일한 키가 있으므로 예외가 발생합니다.

TLDR

요약하면 GetHashCode 메서드는 개체가 저장된 주소를 생성하는 데 사용됩니다. 따라서 사전은 검색 할 필요가 없습니다. 해시 코드를 계산하고 해당 위치로 이동합니다. Equals 방법은 더 나은 평등 테스트이지만 개체를 ​​주소 공간에 매핑하는 데 사용할 수 없습니다.


4
^ 연산자가 무엇인지 궁금해하는 경우 비트 배타적 OR 연산자입니다 ( msdn.microsoft.com/en-us/library/zkacc7k1.aspx 참조) .
R. Schreurs

2
( msdn.microsoft.com/en-us/library/ms132155.aspx ) 구현 자 참고 사항 Equals 메서드가 두 개체 x와 y에 대해 true를 반환하면 값이 반환되도록하려면 구현이 필요합니다. x에 대한 GetHashCode 메소드는 y에 대해 리턴 된 값과 같아야합니다.
Diego Frehner

2
@DiegoFrehner-당신 말이 맞아요. 사람을 트립시킬 수있는 또 다른 사항은 개체가 수정 된 경우 GetHashCode 메서드의 값이 변하지 않아야한다는 것입니다. 따라서 GetHashCode가 의존하는 개체 내의 필드는 읽기 전용 (불변)이어야합니다. 여기에 설명이 있습니다 : stackoverflow.com/a/4868940/469701
sheikhjabootie 10

1
@Acentric : 객체의 해시 코드는 동등성에 영향을주는 방식으로 변경되지 않는 한 변경되지 않아야합니다. 클래스가 동등성에 영향을 미치는 방식으로 변경 될 수있는 경우, 코드는 사전에있는 동안 클래스를 변경하는 코드에 노출 될 수있는 인스턴스를 사전에 저장하지 않아야합니다. 객체를 저장하는 코드가 해당 규칙을 준수하는 경우 변경 가능한 상태를 반영하는 해시 코드를 갖는 것이 유용 할 수 있습니다. 둘 다 유용한 개념이기 때문에 .NET은 상태 평등과 동등성을 더 잘 구별하지 못합니다.
supercat

3
@Acentric : 해시 테이블 주소 지정에 해시 코드를 사용하는 것 외에도 해시 코드의 기본 개념은 두 객체가 서로 다른 해시 코드를 가지고 있다는 사실이 서로 다르고 비교할 필요가 없다는 것을 암시한다는 것입니다. 결론적으로, 많은 객체의 해시 코드가 주어진 객체의 해시 코드와 일치하지 않는다는 지식은 해당 객체와 동일한 객체가 없음을 의미합니다. 주소 지정에 해시 코드를 사용하는 것은 기본적으로 다른 해시 코드가있는 객체를 무시하는 방법입니다.
supercat 2019

9

GetHashCode 는 Dictionary 사전에 사용되며 개체를 저장하기위한 해시를 만듭니다. 다음은 IEqualtyComparerGetHashCode 를 사용하는 이유와 방법에 대한 유용한 기사입니다. http://dotnetperls.com/iequalitycomparer


4
자세히 : Equals 를 비교 해야하는 경우 Enouf를 사용하면 Dictionary에서 요소를 가져와야 할 때 Equals 를 사용하지 않고 해시로 쉽게 수행 할 수 있습니다.
Ash

5

a 및 Dictionary<TKey,TValue>그것의 GetValue유사한 메소드가 Equals저장된 모든 단일 키를 호출 하여 원하는 키와 일치하는지 확인할 수는 있지만 매우 느릴 것입니다. 대신, 많은 해시 기반 컬렉션과 마찬가지로 GetHashCode일치하지 않는 대부분의 값을 고려 대상에서 빠르게 제외 해야합니다 . GetHashCode찾는 아이템을 호출 하면 42가 생성되고 컬렉션에 53,917 개의 아이템이 있지만 GetHashCode53,914를 호출 하면 42 이외의 값이 생성되면 찾는 아이템과 비교할 때 3 개의 아이템 만 비교하면됩니다. 다른 53,914는 무시해도됩니다.

a GetHashCode가 포함 되는 이유는 IEqualityComparer<T>사전 소비자가 일반적으로 서로를 동일하게 간주 하지 않는 동일한 객체로 간주하기를 원할 수 있기 때문입니다 . 가장 일반적인 예는 문자열을 키로 사용하지만 대 / 소문자를 구분하지 않는 호출자입니다. 이 작업을 효율적으로 수행하려면 사전에 "Fox"및 "FOX"에 대해 동일한 값을 생성하지만 "box"또는 "zebra"에 대해 다른 값을 생성하는 해시 함수 형식이 필요합니다. 이후 GetHashCode에 내장 된 방법은 String그런 식으로 작동하지 않습니다, 사전이 다른 곳에서 이러한 방법을 얻을 필요가있을 것이다,IEqualityComparer<T>Equals "Fox"와 "FOX"는 서로 동일하지만 "box"또는 "zebra"는 동일하지 않은 방법입니다.


질문에 대한 정답과 요점 답변! GetHashCode ()는 해당 객체에 대해 Equals ()를 보완해야합니다.
Sumith

@Sumith : 해싱에 대한 많은 토론은 버킷에 대해 이야기하지만 배제에 대해 생각하는 것이 더 유용하다고 생각합니다. 비교가 비싸면 해싱은 버킷으로 구성되지 않은 컬렉션을 사용할 때도 이점을 제공 할 수 있습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.