왜 우리가 왜 커스텀 구조체를 재정의 GetHashCode하고 왜 기본 구현이 "해시 테이블의 키로 사용하기에 적합하지 않은지" 를 설명하는 답변을 찾을 수 Equals없으므로이 블로그에 대한 링크를 남겨 두겠습니다. post 는 문제가 발생한 실제 사례를 설명합니다.
전체 게시물을 읽는 것이 좋지만 여기에 요약이 있습니다 (강조 및 설명 추가).
구조체의 기본 해시가 느리고 좋지 않은 이유 :
CLR이 설계된 방식으로 정의 된 멤버 System.ValueType또는 System.Enum유형 에 대한 모든 호출은 [...] 권투 할당을 유발할 수 있습니다 .
해시 함수의 구현자는 딜레마에 직면합니다. 해시 함수를 적절하게 분배하거나 빠르게 만듭니다. 경우에 따라 두 가지를 모두 달성하는 것이 가능하지만 에서 일반적으로 수행 하기는 어렵 습니다 ValueType.GetHashCode.
구조체의 표준 해시 함수는 모든 필드의 해시 코드를 "결합"합니다. 그러나 ValueType메소드 에서 필드의 해시 코드를 얻는 유일한 방법은 리플렉션 을 사용하는 것 입니다. 따라서 CLR 작성자는 배포판을 통해 속도를 교환하기로 결정했으며 기본 GetHashCode버전 은 null이 아닌 첫 번째 필드의 해시 코드를 반환 하고 유형 ID로 "조정"합니다. [...] 그렇지 않은 경우 합당한 동작입니다 . 예를 들어, 운이 좋지 않고 구조체의 첫 번째 필드가 대부분의 인스턴스에 대해 동일한 값을 갖는 경우 해시 함수는 항상 동일한 결과를 제공합니다 . 그리고 상상할 수 있듯이 이러한 인스턴스가 해시 세트 또는 해시 테이블에 저장된 경우 성능이 크게 저하됩니다.
[...] 리플렉션 기반 구현이 느립니다 . 아주 느린.
[...] 모두 ValueType.Equals와 ValueType.GetHashCode특별한 최적화를 가지고있다. 유형에 "포인터"가없고 적절하게 압축 된 [...] 인 경우보다 최적의 버전이 사용됩니다. GetHashCode인스턴스를 반복하고 4 바이트의 XOR 블록을 Equals사용 하고 메소드 는를 사용하여 두 인스턴스를 비교합니다 memcmp. [...] 그러나 최적화는 매우 까다 롭습니다. 첫째, 최적화가 언제 활성화되는지 알기가 어렵습니다 [...] 둘째, 메모리 비교가 반드시 올바른 결과를 제공하지는 않습니다 . 다음은 간단한 예입니다. [...] -0.0이며 +0.0같지만 이진 표현이 다릅니다.
게시물에 설명 된 실제 문제 :
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
우리는 기본 평등 구현을 가진 커스텀 구조체를 포함하는 튜플을 사용했습니다. 그리고 불행하게도, 구조체는 거의 언제나 [빈 문자열]에 동일 옵션 첫 번째 필드를했다 . 세트의 요소 수가 크게 증가하여 실제 성능 문제가 발생하여 수만 개의 항목으로 콜렉션을 초기화하는 데 몇 분이 소요될 때까지 성능은 정상이었습니다.
따라서 structs 의 경우에는 "어떤 경우에 내 자신의 패키지를 작성하고 어떤 경우에는 기본 구현에 안전하게 의존 할 수 있는지"라는 질문에 대답하려면 structs 를 재정의 Equals하고 GetHashCode사용자 정의 struct가 a로 사용될 때마다 해시 테이블의 키 또는 Dictionary.
또한 IEquatable<T>이 경우 권투를 피하기 위해 구현 하는 것이 좋습니다 .
다른 답변에서 언급했듯이 클래스를 작성하는 경우 참조 동등성을 사용하는 기본 해시가 일반적으로 좋으므로이 경우 재정의가 필요 하지 않은 한이 경우 귀찮게하지 않습니다 Equals( GetHashCode따라서 적절 하게 재정의해야 함 ).