.NET에서 null의 해시 코드가 항상 0이면


87

집합 멤버로 System.Collections.Generic.HashSet<>accept null와 같은 컬렉션이 주어지면 해시 코드가 무엇인지 물어볼 수 null있습니다. 프레임 워크가 0다음을 사용하는 것 같습니다 .

// nullable struct type
int? i = null;
i.GetHashCode();  // gives 0
EqualityComparer<int?>.Default.GetHashCode(i);  // gives 0

// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c);  // gives 0

이것은 nullable 열거 형에서 (약간) 문제가 될 수 있습니다. 우리가 정의한다면

enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

다음은 Nullable<Season>(또한 Season?), 즉 불과 5 값,하지만 그들 중 두 가지를 취할 수 nullSeason.Spring동일한 해시 코드가 있습니다.

다음과 같이 "더 나은"동등 비교자를 작성하고 싶은 유혹이 있습니다.

class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? Default.GetHashCode(x) : -1;
  }
}

그러나 해시 코드가 null있어야 하는 이유 가 0있습니까?

수정 / 추가 :

어떤 사람들은 이것이 재정의에 관한 것이라고 생각하는 것 같습니다 Object.GetHashCode(). 실제로는 그렇지 않습니다. (.NET의 작성자 관련 GetHashCode() 있는 Nullable<>구조체 에서 재정의 했습니다 .) 매개 변수 없는 사용자가 작성한 구현은 우리가 찾는 해시 코드가있는 객체가 .GetHashCode()null

이것은 추상 메서드를 EqualityComparer<T>.GetHashCode(T)구현하거나 인터페이스 메서드를 구현하는 것 IEqualityComparer<T>.GetHashCode(T)입니다. 이제 MSDN에 대한 이러한 링크를 만드는 동안 이러한 메서드 ArgumentNullException가 유일한 인수가 null. 이것은 확실히 MSDN에서 실수입니까? .NET 자체 구현은 예외를 발생시키지 않습니다. 이 경우에 던지는 효과적으로 추가하는 시도 휴식 것이 nullA를을 HashSet<>. 항목을 HashSet<>다룰 때 특별한 일을하지 않는 한 null(나는 그것을 테스트해야 할 것입니다).

새로운 수정 / 추가 :

이제 디버깅을 시도했습니다. 을 사용 HashSet<>하면 기본 같음 비교자를 사용하여 값 Season.Spring과 값 이 동일한 버킷에서 끝날 null 것임을 확인할 수 있습니다 . 이는 전용 배열 멤버 m_bucketsm_slots. 인덱스는 항상 설계 상 1만큼 오프셋됩니다.

그러나 위에서 제공 한 코드는이를 수정하지 않습니다. 결과적으로 HashSet<>값이이면 같음 비교 자에게 묻지 않습니다 null. 이것은 소스 코드에서 가져온 것입니다 HashSet<>.

    // Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
    private int InternalGetHashCode(T item) {
        if (item == null) { 
            return 0;
        } 
        return m_comparer.GetHashCode(item) & Lower31BitMask; 
    }

이는 적어도의 HashSet<>경우 해시를 변경할 수 없음을 의미합니다 null. 대신 해결책은 다음과 같이 다른 모든 값의 해시를 변경하는 것입니다.

class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
  }
}

1
두 번째로 좋은 질문입니다.
Sachin Kainth

26
null의 해시 코드가 0이 아니어야하는 이유는 무엇입니까? 해시 충돌은 세상의 끝이 아닙니다.
Hot Licks

3
잘 알려져 있고 아주 흔한 충돌이라는 점을 제외하면. 아니이 있다는 나쁜 또는 문제의 주요 것을, 그냥 쉽게 피할 수있어
크리스 Pfohl

8
lol 내가 왜 ".NET 프레임 워크가 다리에서 뛰어 내리면 따라가시겠습니까?"라고 생각하고 있습니까?
Adam Houldsworth

3
호기심에서 null 시즌은 무엇입니까?
SwDevMan81

답변:


25

null에 대해 반환 된 해시 코드 가 형식에 대해 일관성 이있는 한 괜찮습니다. 해시 코드에 대한 유일한 요구 사항은 동일한 것으로 간주되는 두 개체가 동일한 해시 코드를 공유한다는 것입니다.

null에 대해 0 또는 -1을 반환하면 하나를 선택하고 항상 반환하는 한 작동합니다. 분명히 null이 아닌 해시 코드는 null에 사용하는 값을 반환해서는 안됩니다.

유제:

null 필드의 GetHashCode?

객체의 식별자가 null 일 때 GetHashCode는 무엇을 반환해야합니까?

MSDN 항목 의 "설명"은 해시 코드에 대해 자세히 설명합니다. 안타깝게도이 문서는 커뮤니티 콘텐츠에서도 null 값 대한 커버리지 나 논의를 전혀 제공하지 않습니다 .

열거 형 문제를 해결하려면 0이 아닌 값을 반환하도록 해시 코드를 다시 구현하거나 null에 해당하는 기본 "알 수없는"열거 형 항목을 추가하거나 단순히 nullable 열거 형을 사용하지 마십시오.

그건 그렇고 흥미로운 발견입니다.

내가 일반적으로 볼 수있는 또 다른 문제는 해시 코드 적어도 한 번의 충돌 없이 nullable이되는 4 바이트 이상의 유형을 나타낼 수 없다는 것입니다 (유형 크기가 증가함에 따라 더 많음 ). 예를 들어 int의 해시 코드는 int 일 뿐이므로 전체 int 범위를 사용합니다. 이 범위에서 어떤 값을 null로 선택합니까? 어떤 것을 선택하든 값의 해시 코드 자체와 충돌합니다.

충돌 자체가 반드시 문제는 아니지만 충돌이 있음을 알아야합니다. 해시 코드는 일부 상황에서만 사용됩니다. MSDN 문서에서 언급했듯이 해시 코드는 다른 개체에 대해 다른 값을 반환한다고 보장되지 않으므로 예상해서는 안됩니다.


나는 당신이 연결하는 질문이 완전히 유사하다고 생각하지 않습니다. Object.GetHashCode()자신의 클래스 (또는 구조체)에서 재정 의 할 때이 코드는 사람들이 실제로 클래스 의 인스턴스 를 가지고있을 때만 히트한다는 것을 알고 있습니다 . 해당 인스턴스는 null. 그것은 당신이 당신의 재정을 시작하지 않는 이유 Object.GetHashCode()if (this == null) return -1;"인 차이있다 null"몇 가지 필드를 가진 객체 인 "및 null".
Jeppe Stig Nielsen

당신은 다음과 같이 말합니다. 당연히 null이 아닌 해시 코드는 null에 사용하는 값을 반환해서는 안됩니다. 그것은 이상적 일 것입니다. 동의합니다. 그리고 우리가 열거 쓰기 할 때마다 때문에 나는 처음부터 내 질문을하는 이유이며 T, 다음 (T?)null(T?)default(T)(.NET의 현재 구현) 같은 해시 코드를가집니다이. NET의 구현자가 .NET의 해시 코드 null 또는 해시 코드 알고리즘을 변경하면 변경 될 수 있습니다 System.Enum.
Jeppe Stig Nielsen

링크가 null 내부 필드에 대한 것이라는 데 동의합니다. IEqualityComparer <T>에 대한 것이라고 언급했는데, 구현에서 해시 코드는 여전히 유형에 따라 다르므로 여전히 동일한 상황, 유형에 대한 일관성에 있습니다. 모든 유형의 null에 대해 동일한 해시 코드를 반환하는 것은 null에 유형이 없기 때문에 중요하지 않습니다.
Adam Houldsworth

1
참고 : 질문을 두 번 업데이트했습니다. (적어도와 함께 HashSet<>) 해시 코드를 변경하는 데 작동하지 않는 것으로 나타났습니다 null.
Jeppe Stig Nielsen

6

해시 코드는 동등성을 결정하는 첫 번째 단계로만 사용되며 두 개체가 동일한 지 여부에 대한 사실상의 결정으로 사용해서는 안됩니다.

두 객체의 해시 코드가 같지 않으면 같지 않은 것으로 처리됩니다 (왜냐하면 우리는 비정상적인 구현이 정확하다고 가정하기 때문입니다. 즉,이를 추측하지 않습니다). 동일한 해시 코드가있는 경우 실제 동일한 지 확인해야 합니다. 귀하의 경우에는 null및 열거 형 값이 실패합니다.

결과적으로 0을 사용하는 것은 일반적인 경우의 다른 값만큼 좋습니다.

물론이 0이 실제 값의 해시 코드 와 공유되는 enum과 같은 상황이있을 것 입니다. 문제는 추가 비교의 작은 오버 헤드가 문제를 일으키는 지 여부입니다.

그렇다면, 특정 유형의 널 (NULL)의 경우에 자신의 비교자를 정의하고, 널 값은 항상 (물론!) 항상 동일한 해시 코드를 얻을 수 있는지 확인 하고 기본에 의해 산출 할 수없는 값을 유형 고유의 해시 코드 알고리즘. 자신의 유형에 대해 이것은 가능합니다. 다른 사람들을 위해-행운을 빕니다 :)


5

그것은하지 않습니다 제로로 - 당신이 42 당신이 원한다면 만들 수 있습니다.

중요한 것은 프로그램 실행 중 일관성 입니다.

null내부적으로 종종 0으로 표시 되기 때문에 가장 명백한 표현 입니다. 즉, 디버깅하는 동안 해시 코드가 0이면 "흠 ..이게 null 참조 문제 였나요?"라고 생각하라는 메시지가 표시 될 수 있습니다.

같은 숫자를 사용하면 0xDEADBEEF누군가 매직 넘버를 사용하고 있다고 말할 수 있습니다. (당신은 0도 마법의 숫자라고 말할 수 있고, 당신은 어느 정도 옳을 것입니다. 규칙에 대한 예외로 널리 사용된다는 점을 제외하면.)


4

좋은 질문.

나는 이것을 코딩하려고했다.

enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

다음과 같이 실행하십시오.

Season? v = null;
Console.WriteLine(v);

그것은 반환 null

내가하면, 대신 정상

Season? v = Season.Spring;
Console.WriteLine((int)v);

그것은 반환 0예상대로, 또는 간단한 우리가지지 않도록 경우 int.

그래서 .. 다음을 수행하면 :

Season? v = Season.Spring;  
Season? vnull = null;   
if(vnull == v) // never TRUE

편집하다

에서 MSDN

두 개체가 동일한 것으로 비교되는 경우 각 개체의 GetHashCode 메서드는 동일한 값을 반환해야합니다. 그러나 두 개체가 같지 않은 경우 두 개체에 대한 GetHashCode 메서드는 다른 값을 반환 할 필요가 없습니다.

즉, 두 객체가 동일한 것을 의미하지 않는 동일한 해시 코드를 가진 경우 실제 동등성은 Equals에 의해 결정됩니다 .

MSDN에서 다시 :

개체에 대한 GetHashCode 메서드는 개체의 Equals 메서드의 반환 값을 결정하는 개체 상태가 수정되지 않는 한 동일한 해시 코드를 일관되게 반환해야합니다. 이것은 현재 응용 프로그램 실행에 대해서만 적용되며 응용 프로그램이 다시 실행되면 다른 해시 코드가 반환 될 수 있습니다.


6
정의에 따라 충돌은 두 개의 다른 객체가 동일한 해시 코드를 가짐을 의미합니다. 개체가 같지 않다는 것을 증명했습니다. 이제 그들은 동일한 해시 코드를 가지고 있습니까? OP에 따르면 이것이 충돌임을 ​​의미합니다. 이제 충돌이 발생하는 것이 세상의 끝이 아닙니다. null이 0이 아닌 다른 값으로 해시되어 성능이 저하되는 것보다 충돌 가능성이 더 높습니다.
Servy

1
그래서 당신의 대답은 실제로 무엇을 말합니까? Season.Spring이 null과 같지 않다고 말합니다. 글쎄, 그것은 틀린 것은 아니지만, 어떤 식 으로든 질문에 실제로 대답하지 않습니다.
Servy

2
@Servy : 질문은 말합니다 : 왜 2 개의 다른 객체 ( nullSpring )에 대해 동일한 hascode가 있습니다. 그래서 대답은 동일한 해시 코드를 가지고 있어도 충돌 원인이 없다는 것입니다. 그런데 그들은 같지 않습니다.
Tigran

3
"답변 : 왜 안돼?" 음, OP는 "왜 안돼"라는 질문에 선제 적으로 대답했습니다. 다른 숫자보다 충돌을 일으킬 가능성이 더 큽니다. 그는 0이 선택된 이유가 있는지 궁금해하고 있었으며 지금까지 아무도 대답하지 않았습니다.
Servy

1
이 답변에는 OP가 아직 알지 못하는 내용이 포함되어 있지 않으며 질문이 요청 된 방식에서 분명합니다.
Konrad Rudolph

4

그러나 null의 해시 코드가 0이어야하는 이유가 있습니까?

그것은 무엇이든 될 수 있습니다. 나는 0이 반드시 최선의 선택이 아니라는 데 동의하는 경향이 있지만 아마도 가장 적은 버그로 이어질 것입니다.

해시 함수 는 반드시 동일한 값에 대해 동일한 해시를 반환 해야합니다 . 이 작업 을 수행 하는 구성 요소가 있으면 이것은 실제로 해시의 유일한 유효한 값입니다 null. hm,과 같이 상수가 있다면를 object.HashOfNull구현하는 누군가 IEqualityComparer가 그 값을 사용해야한다는 것을 알아야합니다. 그들이 그것에 대해 생각하지 않는다면, 그들이 0을 사용할 확률은 다른 모든 값보다 약간 높다고 생각합니다.

적어도 HashSet <>의 경우 null의 해시를 변경할 수도 없습니다.

위에서 언급했듯이 null의 해시가 0이라는 규칙을 이미 따르는 유형이 존재하기 때문에 완전히 불가능하다고 생각합니다.


허용 EqualityComparer<T>.GetHashCode(T)하는 특정 유형에 대한 메소드 를 구현할 때 인수가이면 무언가 를해야 합니다 . (1) 던지거나 , (2) 반환 하거나, (3) 다른 것을 반환 할 수 있습니다. 그 상황에서 항상 추천에 대한 답변을 받습니까? TnullnullArgumentNullException00
Jeppe Stig Nielsen

@JeppeStigNielsen 던지기와 반환에 대해 잘 모르겠지만 반환을 선택하면 확실히 0입니다.
Roman Starkov 2012-06-12

2

단순함을 위해 0입니다. 그러한 어려운 요구 사항은 없습니다. 해시 코딩의 일반적인 요구 사항 만 확인하면됩니다.

예를 들어 두 객체가 같으면 해시 코드도 항상 같아야 합니다 . 따라서 서로 다른 해시 코드는 항상 서로 다른 객체를 나타내야합니다 (그러나 반드시 그 반대의 경우는 아닙니다. 두 개의 서로 다른 객체가 동일한 해시 코드를 가질 수 있습니다. 자주 발생하더라도 이것은 좋은 품질의 해시 함수가 아닙니다. 좋은 충돌 저항).

물론 나는 수학적 성질의 요구 사항에 대한 대답을 제한했습니다. .NET 관련 기술 조건도 있으며 여기에서 읽을 수 있습니다 . 0은 null 값이 아닙니다.


1

따라서 이것은 Unknown열거 형 값 을 사용하여 피할 수 있습니다 (a Season를 알 수없는 것이 약간 이상해 보이지만 ). 따라서 다음과 같이이 문제가 무효화됩니다.

public enum Season
{
   Unknown = 0,
   Spring,
   Summer,
   Autumn,
   Winter
}

Season some_season = Season.Unknown;
int code = some_season.GetHashCode(); // 0
some_season = Season.Autumn;
code = some_season.GetHashCode(); // 3

그러면 각 시즌에 대해 고유 한 해시 코드 값을 갖게됩니다.


1
예, 그러나 이것은 실제로 질문을 대답하지 않습니다. 이런 식 으로 질문에 따라 null은 Uknown과 충돌합니다. 차이점은 무엇입니까?
Tigran

@Tigran-이 버전은 nullable 형식을 사용하지 않습니다
SwDevMan81

하지만 질문은 nullable 유형에 관한 것입니다.
Tigran

나는 사람들이 개선을 위해 제안하는 대답으로 제안하는 장면을 백만 번 가지고 있습니다.
SwDevMan81

1

개인적으로 나는 nullable 값을 사용하는 것이 조금 어색하다는 것을 발견하고 가능할 때마다 피하려고 노력합니다. 귀하의 문제는 또 다른 이유입니다. 때때로 그들은 매우 편리하지만, 내 경험 법칙은 단순히 두 개의 다른 세계에서 왔기 때문에 가능한 경우 값 유형을 null과 혼합하지 않는 것입니다. .NET 프레임 워크에서 그들은 똑같은 일을하는 것 같습니다. 많은 값 유형 TryParse이 값이없는 값 ( null)과 값을 분리하는 방법을 제공 합니다.

특정 경우에는 자신의 Season유형 을 처리하기 때문에 문제를 쉽게 제거 할 수 있습니다 .

(Season?)null내게는 일부 필드가 필요하지 않은 웹 양식이있을 때와 같이 '계절이 지정되지 않았습니다'라는 의미입니다. 제 생각 enum에는 약간 투박한을 사용하는 것보다 그 자체로 특별한 '값'을 지정하는 것이 좋습니다 Nullable<T>. 더 빠르고 (박싱 없음) 더 읽기 쉽고 ( Season.NotSpecifiedvs null) 해시 코드로 문제를 해결할 수 있습니다.

물론 다른 유형의 경우 int값 도메인을 확장 할 수없고 값 중 하나를 특별하게 표시하는 것이 항상 가능한 것은 아닙니다. 그러나 int?해시 코드 충돌은 훨씬 작은 문제입니다.


"boxing"이라고 말하면 "래핑"을 의미한다고 생각합니다. 즉, 구조체 내부에 구조체 값을 넣는 것입니다 Nullable<>( HasValue멤버가로 설정 될 위치 true). 문제가 정말 작 int?습니까? 대부분의 경우 몇 개의 값만 사용 int하면 열거 형 (이론상 많은 구성원을 가질 수 있음)과 동일합니다.
Jeppe Stig Nielsen

일반적으로 필요한 알려진 값의 수가 제한되어있을 때 (2-10) enum이 선택된다고 말하고 싶습니다. 한계가 크거나 없으면 int더 의미가 있습니다. 물론 선호도는 다양합니다.
Maciej

0
Tuple.Create( (object) null! ).GetHashCode() // 0
Tuple.Create( 0 ).GetHashCode() // 0
Tuple.Create( 1 ).GetHashCode() // 1
Tuple.Create( 2 ).GetHashCode() // 2

1
그것은 흥미로운 접근 방식입니다. 추가 설명을 포함하고 특히 질문의 성격을 고려하여 답변을 편집하는 것이 유용합니다.
Jeremy Caney
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.