etc./StackOverFlow

GetHashCode를 재정의하는 가장 좋은 알고리즘은 무엇입니까?

청렴결백한 만능 재주꾼 2022. 2. 7. 06:19
반응형

질문자 :bitbonk


.NET에서 GetHashCode 메서드 는 .NET 기본 클래스 라이브러리의 많은 곳에서 사용됩니다. 컬렉션에서 항목을 빠르게 찾거나 동등성을 결정할 때 적절하게 구현하는 것이 특히 중요합니다.

성능이 저하되지 않도록 사용자 지정 클래스에 대해 GetHashCode 를 구현하는 방법에 대한 표준 알고리즘 또는 모범 사례가 있습니까?



나는 보통 Josh Bloch의 멋진 Effective Java에 제공된 구현과 같은 것을 사용합니다. 빠르고 충돌을 일으키지 않는 꽤 좋은 해시를 생성합니다. 두 개의 다른 소수(예: 17과 23)를 선택하고 다음을 수행합니다.

 public override int GetHashCode() { unchecked // Overflow is fine, just wrap { int hash = 17; // Suitable nullity checks etc, of course :) hash = hash * 23 + field1.GetHashCode(); hash = hash * 23 + field2.GetHashCode(); hash = hash * 23 + field3.GetHashCode(); return hash; } }

의견에서 언급했듯이 대신 곱할 큰 소수를 선택하는 것이 더 낫다는 것을 알 수 있습니다. 분명히 486187739가 좋습니다... 그리고 작은 숫자로 내가 본 대부분의 예는 소수를 사용하는 경향이 있지만 소수가 아닌 숫자가 자주 사용되는 유사한 알고리즘이 있습니다. 예를 들어 나중의 FNV가 아닌 예에서는 분명히 잘 작동하는 숫자를 사용했지만 초기 값은 소수가 아닙니다. (곱셈 상수 소수이지만 그것이 얼마나 중요한지 잘 모르겠습니다.)

이것은 두 가지 주요 이유로 해시 코드를 XOR 하는 일반적인 관행보다 낫습니다. int 필드가 있는 유형이 있다고 가정합니다.

 XorHash(x, x) == XorHash(y, y) == 0 for all x, y XorHash(x, y) == XorHash(y, x) for all x, y

그건 그렇고, 이전 알고리즘은 현재 익명 형식에 대해 C# 컴파일러에서 사용하는 알고리즘입니다.

이 페이지 는 몇 가지 옵션을 제공합니다. 나는 대부분의 경우 위의 내용이 "충분히 좋다"고 생각하며 기억하고 바로잡기가 매우 쉽습니다. FNV 대안은 유사하게 간단하지만 결합 연산으로 ADD XOR 그것은 다음과 같은 코드를 보이지만,이 대신 32 비트 해시 값마다의, 바이트 당 하나의 반복을 수행하기 위해 수정 필요하므로 일반 FNV 알고리즘은, 개별 바이트에서 작동합니다. FNV는 또한 가변 길이의 데이터용으로 설계되었지만 여기에서 사용하는 방식은 항상 동일한 수의 필드 값에 대한 것입니다. 이 답변에 대한 주석은 여기에 있는 코드가 위의 추가 접근 방식만큼 실제로 잘 작동하지 않는다고 제안합니다(테스트된 샘플 사례에서).

 // Note: Not quite FNV! public override int GetHashCode() { unchecked // Overflow is fine, just wrap { int hash = (int) 2166136261; // Suitable nullity checks etc, of course :) hash = (hash * 16777619) ^ field1.GetHashCode(); hash = (hash * 16777619) ^ field2.GetHashCode(); hash = (hash * 16777619) ^ field3.GetHashCode(); return hash; } }

한 가지 알아야 할 것은 이상적으로는 평등에 민감한(따라서 해시코드에 민감한) 상태를 해시 코드에 의존하는 컬렉션에 추가한 후 변경되는 것을 방지해야 한다는 것입니다.

문서에 따라 :

변경할 수 없는 참조 유형에 대해 GetHashCode를 재정의할 수 있습니다. 일반적으로 변경 가능한 참조 유형의 경우 다음과 같은 경우에만 GetHashCode를 재정의해야 합니다.

  • 변경할 수 없는 필드에서 해시 코드를 계산할 수 있습니다. 또는
  • 개체가 해시 코드에 의존하는 컬렉션에 포함되어 있는 동안 변경 가능한 개체의 해시 코드가 변경되지 않도록 할 수 있습니다.

FNV 기사에 대한 링크가 끊어졌지만 여기에 인터넷 아카이브의 사본이 있습니다: Eternally Confuzzled - The Art of Hashing


Jon Skeet

ValueTuple - C# 7용 업데이트

@cactuaroid가 주석에서 언급했듯이 값 튜플을 사용할 수 있습니다. 이것은 몇 가지 키 입력을 절약하고 더 중요하게는 스택에서 순수하게 실행됩니다(가비지 없음).

 (PropA, PropB, PropC, PropD).GetHashCode();

(참고: 익명 유형을 사용하는 원래 기술은 컴파일러에 의해 최적화될 수 있지만 익명 유형이 클래스로 구현되기 때문에 힙에 객체, 즉 쓰레기를 생성하는 것으로 보입니다. 이러한 옵션을 벤치마킹하는 것은 흥미로울 것이지만 튜플 옵션이 더 우수해야 합니다.)

익명 유형(원본 답변)

Microsoft는 이미 훌륭한 일반 HashCode 생성기를 제공합니다. 속성/필드 값을 익명 유형으로 복사하고 해시하면 됩니다.

 new { PropA, PropB, PropC, PropD }.GetHashCode();

이것은 여러 속성에 대해 작동합니다. 권투를 사용하지 않습니다. 익명 유형에 대해 프레임워크에 이미 구현된 알고리즘을 사용합니다.


Rick Love

여기 내 해시 코드 도우미가 있습니다.
장점은 제네릭 형식 인수를 사용하므로 boxing이 발생하지 않는다는 것입니다.

 public static class HashHelper { public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2) { unchecked { return 31 * arg1.GetHashCode() + arg2.GetHashCode(); } } public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3) { unchecked { int hash = arg1.GetHashCode(); hash = 31 * hash + arg2.GetHashCode(); return 31 * hash + arg3.GetHashCode(); } } public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { unchecked { int hash = arg1.GetHashCode(); hash = 31 * hash + arg2.GetHashCode(); hash = 31 * hash + arg3.GetHashCode(); return 31 * hash + arg4.GetHashCode(); } } public static int GetHashCode<T>(T[] list) { unchecked { int hash = 0; foreach (var item in list) { hash = 31 * hash + item.GetHashCode(); } return hash; } } public static int GetHashCode<T>(IEnumerable<T> list) { unchecked { int hash = 0; foreach (var item in list) { hash = 31 * hash + item.GetHashCode(); } return hash; } } /// <summary> /// Gets a hashcode for a collection for that the order of items /// does not matter. /// So {1, 2, 3} and {3, 2, 1} will get same hash code. /// </summary> public static int GetHashCodeForOrderNoMatterCollection<T>( IEnumerable<T> list) { unchecked { int hash = 0; int count = 0; foreach (var item in list) { hash += item.GetHashCode(); count++; } return 31 * hash + count.GetHashCode(); } } /// <summary> /// Alternative way to get a hashcode is to use a fluent /// interface like this:<br /> /// return 0.CombineHashCode(field1).CombineHashCode(field2). /// CombineHashCode(field3); /// </summary> public static int CombineHashCode<T>(this int hashCode, T arg) { unchecked { return 31 * hashCode + arg.GetHashCode(); } }

또한 유창한 인터페이스를 제공하는 확장 메서드가 있으므로 다음과 같이 사용할 수 있습니다.

 public override int GetHashCode() { return HashHelper.GetHashCode(Manufacturer, PartN, Quantity); }

또는 다음과 같이:

 public override int GetHashCode() { return 0.CombineHashCode(Manufacturer) .CombineHashCode(PartN) .CombineHashCode(Quantity); }

nightcoder

.NET 표준 2.1 이상

.NET Standard 2.1 이상을 사용하는 경우 System.HashCode 구조체를 사용할 수 있습니다. 두 가지 사용 방법이 있습니다.

해시코드.결합

Combine 메서드를 사용하여 최대 8개의 개체가 제공되는 해시 코드를 만들 수 있습니다.

 public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

해시코드.추가

Add 메서드는 컬렉션을 처리하는 데 도움이 됩니다.

 public override int GetHashCode() { var hashCode = new HashCode(); hashCode.Add(this.object1); foreach (var item in this.collection) { hashCode.Add(item); } return hashCode.ToHashCode(); }

GetHashCode가 쉬워졌습니다

자세한 내용과 의견은 전체 블로그 게시물 ' GetHashCode Made Easy '를 참조하세요.

사용 예

 public class SuperHero { public int Age { get; set; } public string Name { get; set; } public List<string> Powers { get; set; } public override int GetHashCode() => HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers); }

구현

 public struct HashCode : IEquatable<HashCode> { private const int EmptyCollectionPrimeNumber = 19; private readonly int value; private HashCode(int value) => this.value = value; public static implicit operator int(HashCode hashCode) => hashCode.value; public static bool operator ==(HashCode left, HashCode right) => left.Equals(right); public static bool operator !=(HashCode left, HashCode right) => !(left == right); public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item)); public static HashCode OfEach<T>(IEnumerable<T> items) => items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0)); public HashCode And<T>(T item) => new HashCode(CombineHashCodes(this.value, GetHashCode(item))); public HashCode AndEach<T>(IEnumerable<T> items) { if (items == null) { return new HashCode(this.value); } return new HashCode(GetHashCode(items, this.value)); } public bool Equals(HashCode other) => this.value.Equals(other.value); public override bool Equals(object obj) { if (obj is HashCode) { return this.Equals((HashCode)obj); } return false; } public override int GetHashCode() => this.value.GetHashCode(); private static int CombineHashCodes(int h1, int h2) { unchecked { // Code copied from System.Tuple a good way to combine hashes. return ((h1 << 5) + h1) ^ h2; } } private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0; private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode) { var temp = startHashCode; var enumerator = items.GetEnumerator(); if (enumerator.MoveNext()) { temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); while (enumerator.MoveNext()) { temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); } } else { temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber); } return temp; } }

무엇이 좋은 알고리즘을 만드는가?

성능

해시 코드를 계산하는 알고리즘은 빨라야 합니다. 간단한 알고리즘은 일반적으로 더 빠른 알고리즘이 될 것입니다. 추가 메모리를 할당하지 않는 메모리는 가비지 수집의 필요성을 줄여 성능을 향상시킵니다.

특히 C# 해시 함수에서는 성능 향상을 위해 오버플로 검사를 중지 unchecked

결정론적

해싱 알고리즘은 결정적 이어야 합니다. 즉, 동일한 입력이 주어지면 항상 동일한 출력을 생성해야 합니다.

충돌 감소

해시 코드를 계산하는 알고리즘은 해시 충돌 을 최소화해야 합니다. 해시 충돌은 두 개의 서로 다른 개체 GetHashCode 에 대한 두 번의 호출이 동일한 해시 코드를 생성할 때 발생하는 상황입니다. 충돌은 허용되지만(일부는 그렇지 않다는 오해가 있음) 최소한으로 유지해야 합니다.

17 또는 23 과 같은 마법의 숫자가 포함되어 있습니다. 이들은 특별한 소수 수학적 속성 도움말로 인해 비 소수를 사용하여 비교 해시 충돌을 줄일 수 있습니다.

해시 균일성

좋은 해시 함수는 예상 입력을 출력 범위에 걸쳐 가능한 한 고르게 매핑해야 합니다. 즉, 고르게 분포된 입력을 기반으로 광범위한 해시를 출력해야 합니다. 해시 균일성이 있어야 합니다.

예방의 DoS

.NET Core에서는 애플리케이션을 다시 시작할 때마다 다른 해시 코드를 받게 됩니다. 서비스 거부 공격(DoS)을 방지하기 위한 보안 기능입니다. .NET Framework의 경우 다음 App.config 파일을 추가하여 이 기능을 활성화 해야 합니다.

 <?xml version ="1.0"?> <configuration> <runtime> <UseRandomizedStringHashAlgorithm enabled="1" /> </runtime> </configuration>

이 기능으로 인해 해시 코드는 생성된 애플리케이션 도메인 외부에서 사용해서는 안 되며 컬렉션의 키 필드로 사용되어서도 안 되며 지속되어서도 안 됩니다.

여기 에 대해 자세히 알아보십시오.

암호화 보안?

알고리즘은 암호화 해시 함수일 필요는 없습니다. 즉, 다음 조건을 충족할 필요가 없습니다.

  • 주어진 해시 값을 생성하는 메시지를 생성하는 것은 불가능합니다.
  • 동일한 해시 값을 가진 두 개의 다른 메시지를 찾는 것은 불가능합니다.
  • 메시지에 대한 작은 변경은 해시 값을 너무 광범위하게 변경하여 새 해시 값이 이전 해시 값과 상관 관계가 없는 것처럼 보이게 해야 합니다(사태 효과).

Muhammad Rehan Saeed

이 목적으로 사용하는 Helper 라이브러리에 Hashing 클래스가 있습니다.

 /// <summary> /// This is a simple hashing function from Robert Sedgwicks Hashing in C book. /// Also, some simple optimizations to the algorithm in order to speed up /// its hashing process have been added. from: www.partow.net /// </summary> /// <param name="input">array of objects, parameters combination that you need /// to get a unique hash code for them</param> /// <returns>Hash code</returns> public static int RSHash(params object[] input) { const int b = 378551; int a = 63689; int hash = 0; // If it overflows then just wrap around unchecked { for (int i = 0; i < input.Length; i++) { if (input[i] != null) { hash = hash * a + input[i].GetHashCode(); a = a * b; } } } return hash; }

그런 다음 간단히 다음과 같이 사용할 수 있습니다.

 public override int GetHashCode() { return Hashing.RSHash(_field1, _field2, _field3); }

성능을 평가하지 않았으므로 어떤 피드백이든 환영합니다.


Wahid Shalaly

다음은 Jon Skeet의 구현을 사용하는 도우미 클래스입니다.

 public static class HashCode { public const int Start = 17; public static int Hash<T>(this int hash, T obj) { var h = EqualityComparer<T>.Default.GetHashCode(obj); return unchecked((hash * 31) + h); } }

용법:

 public override int GetHashCode() { return HashCode.Start .Hash(_field1) .Hash(_field2) .Hash(_field3); }

System.Int32에 대한 확장 메서드를 작성하지 않으려면:

 public readonly struct HashCode { private readonly int _value; public HashCode(int value) => _value = value; public static HashCode Start { get; } = new HashCode(17); public static implicit operator int(HashCode hash) => hash._value; public HashCode Hash<T>(T obj) { var h = EqualityComparer<T>.Default.GetHashCode(obj); return unchecked(new HashCode((_value * 31) + h)); } public override int GetHashCode() => _value; }

여전히 힙 할당을 피하고 정확히 같은 방식으로 사용됩니다.

 public override int GetHashCode() { // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance. // And the result is implicitly converted to `Int32`. return HashCode.Start .Hash(_field1) .Hash(_field2) .Hash(_field3); }

편집(2018년 5월): EqualityComparer<T>.Default getter는 이제 JIT 내장입니다. 끌어오기 요청 은 Stephen Toub이 이 블로그 게시물 에서 언급했습니다.


Şafak Gür

Equals()가 여러 필드를 비교하는 대부분의 경우 GetHash()가 한 필드 또는 여러 필드에서 해시되는지 여부는 실제로 중요하지 않습니다. 해시 계산이 정말 저렴하고( 할당 없음 , 제발) 빠르고( 많은 계산 과 데이터베이스 연결이 없음) 좋은 분포를 제공하는지 확인하기만 하면 됩니다.

무거운 물건은 Equals() 메서드의 일부여야 합니다. 해시는 가능한 적은 수의 항목에 대해 Equals()를 호출할 수 있도록 하는 매우 저렴한 작업이어야 합니다.

그리고 마지막 팁: 여러 응용 프로그램 실행에서 GetHashCode()가 안정적이라고 의존하지 마십시오 . 많은 .Net 유형은 다시 시작한 후에도 해시 코드가 동일하게 유지되도록 보장하지 않으므로 메모리 데이터 구조에서 GetHashCode() 값만 사용해야 합니다.


Bert Huijben

최근까지 내 대답은 Jon Skeet의 여기와 매우 가까웠을 것입니다. 그러나 저는 최근에 내부 테이블의 크기가 8, 16, 32 등인 해시 테이블인 2의 거듭제곱 해시 테이블을 사용하는 프로젝트를 시작했습니다. 소수 크기를 선호하는 데에는 타당한 이유가 있지만, 2의 거듭제곱 크기에도 몇 가지 장점이 있습니다.

그리고 그것은 꽤 빨랐다. 그래서 약간의 실험과 연구 끝에 다음과 같이 해시를 다시 해시하기 시작했습니다.

 public static int ReHash(int source) { unchecked { ulong c = 0xDEADBEEFDEADBEEF + (ulong)source; ulong d = 0xE2ADBEEFDEADBEEF ^ c; ulong a = d += c = c << 15 | c >> -15; ulong b = a += d = d << 52 | d >> -52; c ^= b += a = a << 26 | a >> -26; d ^= c += b = b << 51 | b >> -51; a ^= d += c = c << 28 | c >> -28; b ^= a += d = d << 9 | d >> -9; c ^= b += a = a << 47 | a >> -47; d ^= c += b << 54 | b >> -54; a ^= d += c << 32 | c >> 32; a += d << 25 | d >> -25; return (int)(a >> 1); } }

그리고 내 2의 거듭제곱 해시 테이블은 더 이상 쓸모가 없었습니다.

위의 작업이 작동하지 않아야하기 때문에 이것은 나를 방해했습니다. GetHashCode() 가 매우 특정한 방식으로 좋지 않은 경우가 아니면 작동하지 않아야 합니다.

해시코드를 재혼합하는 것은 훌륭한 해시코드를 향상시킬 수 없습니다. 가능한 효과는 충돌을 몇 개 더 도입하는 것뿐이기 때문입니다.

해시 코드를 다시 혼합해도 끔찍한 해시 코드를 개선할 수 없습니다. 가능한 효과는 예를 들어 값 53에 대한 많은 충돌을 많은 수의 값 18,3487,291로 변경하는 것이기 때문입니다.

해시 코드를 재혼합하면 해당 범위(2 32개의 가능한 값) 전체에서 절대 충돌을 피하는 데는 상당히 잘했지만 해시 테이블에서 실제 사용을 위해 모듈화할 때 충돌을 피하는 데는 좋지 않은 해시 코드만 개선할 수 있습니다. 2의 거듭제곱 테이블의 더 간단한 모듈로가 이를 더 분명하게 만들었지만, 더 일반적인 소수 테이블에 부정적인 영향을 미치기도 했습니다. , 그러나 이점은 여전히 존재합니다).

편집: 나는 또한 2의 거듭제곱이라는 사실보다 충돌에 대한 민감도를 증가시킬 열린 주소 지정을 사용하고 있었습니다.

그리고 .NET (또는 여기에서 string.GetHashCode() 구현이 이러한 방식으로(충돌이 적기 때문에 약 20-30배 더 빠르게 실행되는 테스트 순서로) 개선될 수 있다는 사실이 혼란스러웠습니다. 내 자신의 해시 코드를 훨씬 더 많이 개선할 수 있습니다.

내가 과거에 코딩했고 실제로 이 사이트에서 답변의 기초로 사용했던 모든 GetHashCode() 구현은 내가 겪은 것보다 훨씬 나빴습니다 . 대부분의 경우 많은 용도에 대해 "충분히 좋은" 것이지만 더 나은 것을 원했습니다.

그래서 저는 그 프로젝트를 한쪽으로 치우고(어쨌든 애완용 프로젝트였습니다) .NET에서 훌륭하고 잘 분산된 해시 코드를 빠르게 생성하는 방법을 찾기 시작했습니다.

결국 나는 SpookyHash 를 .NET으로 이식하기로 결정했습니다. 실제로 위의 코드는 SpookyHash를 사용하여 32비트 입력에서 32비트 출력을 생성하는 빠른 경로 버전입니다.

이제 SpookyHash는 코드를 빠르게 기억할 수 없습니다. 더 나은 속도*를 위해 많은 포트를 수동으로 입력했기 때문에 포트가 훨씬 적습니다. 하지만 그것이 바로 코드 재사용의 목적입니다.

그런 다음 원래 프로젝트에서 더 나은 해시 코드를 생성하는 방법에 대한 질문을 생성한 것처럼 해당 프로젝트에서 더 나은 .NET memcpy를 생성하는 방법에 대한 질문을 생성했기 때문에 해당 프로젝트를 한쪽으로 치워두었습니다.

decimal † 제외)을 해시 코드에 쉽게 공급하기 위해 많은 오버로드를 생성했습니다.

내가 이식한 원본 코드가 특히 알고리즘이 최적화된 64비트 시스템에서 여전히 더 빠르기 때문에 Bob Jenkins는 대부분의 공로를 인정받아야 합니다.

전체 코드는 https://bitbucket.org/JonHanna/spookilysharp/src 에서 볼 수 있지만 위의 코드는 단순화된 버전입니다.

그러나 이제 이미 작성되었으므로 더 쉽게 사용할 수 있습니다.

 public override int GetHashCode() { var hash = new SpookyHash(); hash.Update(field1); hash.Update(field2); hash.Update(field3); return hash.Final().GetHashCode(); }

또한 시드 값을 사용하므로 신뢰할 수 없는 입력을 처리해야 하고 Hash DoS 공격으로부터 보호하려면 가동 시간 또는 이와 유사한 것을 기반으로 시드를 설정하고 공격자가 결과를 예측할 수 없도록 만들 수 있습니다.

 private static long hashSeed0 = Environment.TickCount; private static long hashSeed1 = DateTime.Now.Ticks; public override int GetHashCode() { //produce different hashes ever time this application is restarted //but remain consistent in each run, so attackers have a harder time //DoSing the hash tables. var hash = new SpookyHash(hashSeed0, hashSeed1); hash.Update(field1); hash.Update(field2); hash.Update(field3); return hash.Final().GetHashCode(); }

*여기서 가장 놀라운 점은 (x << n) | (x >> -n) 개선된 사항. 나는 지터가 나를 위해 그것을 인라인했을 것이라고 확신했지만 프로파일링은 그렇지 않았다.

decimal 는 C#에서 가져온 것이지만 .NET 관점에서 네이티브가 아닙니다. 문제는 자체 GetHashCode() 정밀도를 중요한 것으로 취급하지만 자체 Equals() 는 그렇지 않다는 것입니다. 둘 다 유효한 선택이지만 그렇게 혼합되지는 않습니다. 자신의 버전을 구현하려면 둘 중 하나를 선택해야 하지만 원하는 버전을 알 수 없습니다.

‡비교용입니다. 스트링을 사용하는 경우, 64 비트에 SpookyHash 상당히 빨리보다 string.GetHashCode() 약간 빠른보다 32 비트에 string.GetHashCode() 32 개 비트를 상당히 빠르게 SpookyHash보다 64 비트에, 그러나 여전히 고속 충분히 합리적인 선택입니다.


Jon Hanna

https://github.com/dotnet/coreclr/pull/14863 부터 매우 간단한 해시 코드를 생성하는 새로운 방법이 있습니다! 그냥 써

 public override int GetHashCode() => HashCode.Combine(field1, field2, field3);

이것은 구현 세부 사항에 대해 걱정할 필요 없이 품질 해시 코드를 생성합니다.


James Ko

이것은 좋은 것입니다:

 /// <summary> /// Helper class for generating hash codes suitable /// for use in hashing algorithms and data structures like a hash table. /// </summary> public static class HashCodeHelper { private static int GetHashCodeInternal(int key1, int key2) { unchecked { var num = 0x7e53a269; num = (-1521134295 * num) + key1; num += (num << 10); num ^= (num >> 6); num = ((-1521134295 * num) + key2); num += (num << 10); num ^= (num >> 6); return num; } } /// <summary> /// Returns a hash code for the specified objects /// </summary> /// <param name="arr">An array of objects used for generating the /// hash code.</param> /// <returns> /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. /// </returns> public static int GetHashCode(params object[] arr) { int hash = 0; foreach (var item in arr) hash = GetHashCodeInternal(hash, item.GetHashCode()); return hash; } /// <summary> /// Returns a hash code for the specified objects /// </summary> /// <param name="obj1">The first object.</param> /// <param name="obj2">The second object.</param> /// <param name="obj3">The third object.</param> /// <param name="obj4">The fourth object.</param> /// <returns> /// A hash code, suitable for use in hashing algorithms and /// data structures like a hash table. /// </returns> public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3, T4 obj4) { return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4)); } /// <summary> /// Returns a hash code for the specified objects /// </summary> /// <param name="obj1">The first object.</param> /// <param name="obj2">The second object.</param> /// <param name="obj3">The third object.</param> /// <returns> /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. /// </returns> public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3) { return GetHashCode(obj1, GetHashCode(obj2, obj3)); } /// <summary> /// Returns a hash code for the specified objects /// </summary> /// <param name="obj1">The first object.</param> /// <param name="obj2">The second object.</param> /// <returns> /// A hash code, suitable for use in hashing algorithms and data /// structures like a hash table. /// </returns> public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2) { return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode()); } }

사용 방법은 다음과 같습니다.

 private struct Key { private Type _type; private string _field; public Type Type { get { return _type; } } public string Field { get { return _field; } } public Key(Type type, string field) { _type = type; _field = field; } public override int GetHashCode() { return HashCodeHelper.GetHashCode(_field, _type); } public override bool Equals(object obj) { if (!(obj is Key)) return false; var tf = (Key)obj; return tf._field.Equals(_field) && tf._type.Equals(_type); } }

Magnus

다음은 Jon Skeet이 게시한 알고리즘 의 또 다른 유창한 구현이지만 할당이나 박싱 작업은 포함하지 않습니다.

 public static class Hash { public const int Base = 17; public static int HashObject(this int hash, object obj) { unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); } } public static int HashValue<T>(this int hash, T value) where T : struct { unchecked { return hash * 23 + value.GetHashCode(); } } }

용법:

 public class MyType<T> { public string Name { get; set; } public string Description { get; set; } public int Value { get; set; } public IEnumerable<T> Children { get; set; } public override int GetHashCode() { return Hash.Base .HashObject(this.Name) .HashObject(this.Description) .HashValue(this.Value) .HashObject(this.Children); } }

컴파일러는 HashValue 가 클래스와 함께 호출되지 않도록 합니다. 그러나 일반 인수를 추가하면 boxing 작업도 추가되기 때문에 HashObject 대한 컴파일러 지원은 없습니다.


Scott Wegner

여기 내 단순한 접근 방식이 있습니다. 나는 이것을 위해 고전적인 빌더 패턴을 사용하고 있습니다. 유형이 안전하고(박싱/언박싱 없음) .NET 2.0(확장 메서드 등 없음)과도 호환됩니다.

다음과 같이 사용됩니다.

 public override int GetHashCode() { HashBuilder b = new HashBuilder(); b.AddItems(this.member1, this.member2, this.member3); return b.Result; }

다음은 acutal 빌더 클래스입니다.

 internal class HashBuilder { private const int Prime1 = 17; private const int Prime2 = 23; private int result = Prime1; public HashBuilder() { } public HashBuilder(int startHash) { this.result = startHash; } public int Result { get { return this.result; } } public void AddItem<T>(T item) { unchecked { this.result = this.result * Prime2 + item.GetHashCode(); } } public void AddItems<T1, T2>(T1 item1, T2 item2) { this.AddItem(item1); this.AddItem(item2); } public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); } public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, T4 item4) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); this.AddItem(item4); } public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) { this.AddItem(item1); this.AddItem(item2); this.AddItem(item3); this.AddItem(item4); this.AddItem(item5); } public void AddItems<T>(params T[] items) { foreach (T item in items) { this.AddItem(item); } } }

bitbonk

속성이 8개 이하인 경우(잘하면) 다른 대안이 있습니다.

ValueTuple 은 구조체이며 확실한 GetHashCode 구현이 있는 것으로 보입니다.

즉, 간단히 다음과 같이 할 수 있습니다.

 // Yay, no allocations and no custom implementations! public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

ValueTuple 의 GetHashCode 대한 .NET Core의 현재 구현을 ValueTuple .

이것은 ValueTuple .

 internal static int CombineHashCodes(int h1, int h2) { return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2); } internal static int CombineHashCodes(int h1, int h2, int h3) { return HashHelpers.Combine(CombineHashCodes(h1, h2), h3); }

그리고 이것은 HashHelper .

 public static readonly int RandomSeed = Guid.NewGuid().GetHashCode(); public static int Combine(int h1, int h2) { unchecked { // RyuJIT optimizes this to use the ROL instruction // Related GitHub pull request: dotnet/coreclr#1830 uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); return ((int)rol5 + h1) ^ h2; } }

영어로:

  • 5 위치만큼 왼쪽 회전(원형 이동) h1.
  • 결과와 h1을 더합니다.
  • 결과를 h2로 XOR합니다.
  • { static random seed, h1 }에 대해 위의 작업을 수행하여 시작합니다.
  • 각 추가 항목에 대해 이전 결과와 다음 항목(예: h2)에 대한 작업을 수행합니다.

이 ROL-5 해시 코드 알고리즘의 속성에 대해 더 많이 아는 것이 좋습니다.

유감스럽게도 자체 GetHashCode ValueTuple 을 연기하는 것은 우리가 원하고 기대하는 것만큼 빠르지 않을 수 있습니다. 관련 토론에서 이 주석은 HashHelpers.Combine 을 직접 호출하는 것이 더 성능이 좋다는 것을 보여줍니다. 반대로, 내부 코드이므로 여기에서 얻은 것의 많은 부분을 희생하면서 코드를 복사해야 합니다. 또한, 우리는 먼저 무작위 시드와 Combine 그 단계를 건너뛰면 어떤 결과가 초래될지 모릅니다.


Timo

ReSharper ReSharper -> Edit -> Generate Code -> Equality Members 하여 GetHashCode, Equals 및 기타 항목을 생성할 수 있습니다.

 // ReSharper's GetHashCode looks like this public override int GetHashCode() { unchecked { int hashCode = Id; hashCode = (hashCode * 397) ^ IntMember; hashCode = (hashCode * 397) ^ OtherIntMember; hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0); // ... return hashCode; } }

Charles Burns

내 작업의 대부분은 데이터베이스 연결로 수행됩니다. 즉, 내 클래스에는 모두 데이터베이스의 고유 식별자가 있습니다. 나는 항상 데이터베이스의 ID를 사용하여 해시 코드를 생성합니다.

 // Unique ID from database private int _id; ... { return _id.GetHashCode(); }

Mark G

원하는 경우 소수를 올리는 것이 더 쉽다는 점을 제외하고는 nightcoder의 솔루션과 거의 유사합니다.

추신: 이것은 9개의 기본값이 있는 하나의 방법으로 리팩토링될 수 있다는 것을 알고 입에 약간 토하는 경우 중 하나입니다. 하지만 더 느리므로 눈을 감고 잊어버리려고 합니다.

 /// <summary> /// Try not to look at the source code. It works. Just rely on it. /// </summary> public static class HashHelper { private const int PrimeOne = 17; private const int PrimeTwo = 23; public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); hash = hash * PrimeTwo + arg9.GetHashCode(); hash = hash * PrimeTwo + arg10.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); hash = hash * PrimeTwo + arg9.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); hash = hash * PrimeTwo + arg8.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); hash = hash * PrimeTwo + arg7.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); hash = hash * PrimeTwo + arg6.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); hash = hash * PrimeTwo + arg5.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); hash = hash * PrimeTwo + arg4.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); hash = hash * PrimeTwo + arg3.GetHashCode(); return hash; } } public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2) { unchecked { int hash = PrimeOne; hash = hash * PrimeTwo + arg1.GetHashCode(); hash = hash * PrimeTwo + arg2.GetHashCode(); return hash; } } }

Dbl

여러 해싱 방법을 선도하는 Microsoft ...

 //for classes that contain a single int value return this.value; //for classes that contain multiple int value return x ^ y; //for classes that contain single number bigger than int return ((int)value ^ (int)(value >> 32)); //for classes that contain class instance fields which inherit from object return obj1.GetHashCode(); //for classes that contain multiple class instance fields which inherit from object return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode();

여러 big int에 대해 다음을 사용할 수 있다고 추측할 수 있습니다.

 int a=((int)value1 ^ (int)(value1 >> 32)); int b=((int)value2 ^ (int)(value2 >> 32)); int c=((int)value3 ^ (int)(value3 >> 32)); return a ^ b ^ c;

그리고 다중 유형의 경우에도 동일합니다. 먼저 GetHashCode() 를 사용하여 int 로 변환한 다음 int 값은 xor'되고 결과는 해시입니다.

해시를 아이디(고유값을 의미)로 사용하는 사람들에게 해시는 당연히 자릿수로 제한되는데, 해시 알고리즘의 경우 최소 MD5의 경우 5바이트였다고 생각합니다.

여러 값을 해시 값으로 바꿀 수 있으며 그 중 일부는 동일하므로 식별자로 사용하지 마십시오. (언젠가 나는 당신의 구성 요소를 사용할 것입니다)


deadManN

위의 답변으로 선택한 구현을 사용하여 부동 소수점 및 소수에 문제가 발생했습니다.

이 테스트는 실패합니다(플로트, 해시는 2개의 값을 음수로 전환했지만 동일함).

 var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m}; var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m}; var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D); var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D); Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different hash1:{0} hash2:{1}",hash1,hash2));

그러나 이 테스트는 통과합니다(int 사용).

 var obj1 = new { A = 100m, B = 100m, C = 100, D = 100}; var obj2 = new { A = 100m, B = 100m, C = -100, D = -100}; var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D); var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D); Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different hash1:{0} hash2:{1}",hash1,hash2));

기본 유형에 GetHashCode를 사용하지 않도록 구현을 변경했는데 더 잘 작동하는 것 같습니다.

 private static int InternalComputeHash(params object[] obj) { unchecked { var result = (int)SEED_VALUE_PRIME; for (uint i = 0; i < obj.Length; i++) { var currval = result; var nextval = DetermineNextValue(obj[i]); result = (result * MULTIPLIER_VALUE_PRIME) + nextval; } return result; } } private static int DetermineNextValue(object value) { unchecked { int hashCode; if (value is short || value is int || value is byte || value is sbyte || value is uint || value is ushort || value is ulong || value is long || value is float || value is double || value is decimal) { return Convert.ToInt32(value); } else { return value != null ? value.GetHashCode() : 0; } } }

HokieMike

이것은 Josh Bloch의 구현을 구현하는 정적 도우미 클래스입니다. 복싱을 "방지"하고 특히 긴 프리미티브에 대한 해시를 구현하기 위해 명시적 오버로드를 제공합니다.

equals 구현과 일치하는 문자열 비교를 전달할 수 있습니다.

해시 출력은 항상 int이므로 해시 호출을 연결할 수 있습니다.

 using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; namespace Sc.Util.System { /// <summary> /// Static methods that allow easy implementation of hashCode. Example usage: /// <code> /// public override int GetHashCode() /// => HashCodeHelper.Seed /// .Hash(primitiveField) /// .Hsh(objectField) /// .Hash(iEnumerableField); /// </code> /// </summary> public static class HashCodeHelper { /// <summary> /// An initial value for a hashCode, to which is added contributions from fields. /// Using a non-zero value decreases collisions of hashCode values. /// </summary> public const int Seed = 23; private const int oddPrimeNumber = 37; /// <summary> /// Rotates the seed against a prime number. /// </summary> /// <param name="aSeed">The hash's first term.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int rotateFirstTerm(int aSeed) { unchecked { return HashCodeHelper.oddPrimeNumber * aSeed; } } /// <summary> /// Contributes a boolean to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aBoolean">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, bool aBoolean) { unchecked { return HashCodeHelper.rotateFirstTerm(aSeed) + (aBoolean ? 1 : 0); } } /// <summary> /// Contributes a char to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aChar">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, char aChar) { unchecked { return HashCodeHelper.rotateFirstTerm(aSeed) + aChar; } } /// <summary> /// Contributes an int to the developing HashCode seed. /// Note that byte and short are handled by this method, through implicit conversion. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aInt">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, int aInt) { unchecked { return HashCodeHelper.rotateFirstTerm(aSeed) + aInt; } } /// <summary> /// Contributes a long to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aLong">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, long aLong) { unchecked { return HashCodeHelper.rotateFirstTerm(aSeed) + (int)(aLong ^ (aLong >> 32)); } } /// <summary> /// Contributes a float to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aFloat">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, float aFloat) { unchecked { return HashCodeHelper.rotateFirstTerm(aSeed) + Convert.ToInt32(aFloat); } } /// <summary> /// Contributes a double to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aDouble">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, double aDouble) => aSeed.Hash(Convert.ToInt64(aDouble)); /// <summary> /// Contributes a string to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aString">The value to contribute.</param> /// <param name="stringComparison">Optional comparison that creates the hash.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash( this int aSeed, string aString, StringComparison stringComparison = StringComparison.Ordinal) { if (aString == null) return aSeed.Hash(0); switch (stringComparison) { case StringComparison.CurrentCulture : return StringComparer.CurrentCulture.GetHashCode(aString); case StringComparison.CurrentCultureIgnoreCase : return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString); case StringComparison.InvariantCulture : return StringComparer.InvariantCulture.GetHashCode(aString); case StringComparison.InvariantCultureIgnoreCase : return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString); case StringComparison.OrdinalIgnoreCase : return StringComparer.OrdinalIgnoreCase.GetHashCode(aString); default : return StringComparer.Ordinal.GetHashCode(aString); } } /// <summary> /// Contributes a possibly-null array to the developing HashCode seed. /// Each element may be a primitive, a reference, or a possibly-null array. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aArray">CAN be null.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, IEnumerable aArray) { if (aArray == null) return aSeed.Hash(0); int countPlusOne = 1; // So it differs from null foreach (object item in aArray) { ++countPlusOne; if (item is IEnumerable arrayItem) { if (!object.ReferenceEquals(aArray, arrayItem)) aSeed = aSeed.Hash(arrayItem); // recursive call! } else aSeed = aSeed.Hash(item); } return aSeed.Hash(countPlusOne); } /// <summary> /// Contributes a possibly-null array to the developing HashCode seed. /// You must provide the hash function for each element. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aArray">CAN be null.</param> /// <param name="hashElement">Required: yields the hash for each element /// in <paramref name="aArray"/>.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement) { if (aArray == null) return aSeed.Hash(0); int countPlusOne = 1; // So it differs from null foreach (T item in aArray) { ++countPlusOne; aSeed = aSeed.Hash(hashElement(item)); } return aSeed.Hash(countPlusOne); } /// <summary> /// Contributes a possibly-null object to the developing HashCode seed. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="aObject">CAN be null.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Hash(this int aSeed, object aObject) { switch (aObject) { case null : return aSeed.Hash(0); case bool b : return aSeed.Hash(b); case char c : return aSeed.Hash(c); case int i : return aSeed.Hash(i); case long l : return aSeed.Hash(l); case float f : return aSeed.Hash(f); case double d : return aSeed.Hash(d); case string s : return aSeed.Hash(s); case IEnumerable iEnumerable : return aSeed.Hash(iEnumerable); } return aSeed.Hash(aObject.GetHashCode()); } /// <summary> /// This utility method uses reflection to iterate all specified properties that are readable /// on the given object, excluding any property names given in the params arguments, and /// generates a hashcode. /// </summary> /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use /// the <see cref="Seed"/>.</param> /// <param name="aObject">CAN be null.</param> /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param> /// <param name="ignorePropertyNames">Optional.</param> /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int HashAllProperties( this int aSeed, object aObject, BindingFlags propertySelector = BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty, params string[] ignorePropertyNames) { if (aObject == null) return aSeed.Hash(0); if ((ignorePropertyNames != null) && (ignorePropertyNames.Length != 0)) { foreach (PropertyInfo propertyInfo in aObject.GetType() .GetProperties(propertySelector)) { if (!propertyInfo.CanRead || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0)) continue; aSeed = aSeed.Hash(propertyInfo.GetValue(aObject)); } } else { foreach (PropertyInfo propertyInfo in aObject.GetType() .GetProperties(propertySelector)) { if (propertyInfo.CanRead) aSeed = aSeed.Hash(propertyInfo.GetValue(aObject)); } } return aSeed; } /// <summary> /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER, /// this method has a different name since it will not be automatically invoked by /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>, /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise /// the generated hash code will not be consistent. This method itself ALSO will not invoke /// this method on the Key or Value here if that itself is a KeyValuePair. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="keyValuePair">The value to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair) => aSeed.Hash(keyValuePair.Key) .Hash(keyValuePair.Value); /// <summary> /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/> /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER, /// this method has a different name since it will not be automatically invoked by /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>, /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise /// the generated hash code will not be consistent. This method itself ALSO will not invoke /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of /// KeyValuePair. /// </summary> /// <param name="aSeed">The developing HashCode value or seed.</param> /// <param name="keyValuePairs">The values to contribute.</param> /// <returns>The new hash code.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int HashKeysAndValues<TKey, TValue>( this int aSeed, IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) { if (keyValuePairs == null) return aSeed.Hash(null); foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) { aSeed = aSeed.HashKeyAndValue(keyValuePair); } return aSeed; } } }

Steven Coco

HashCodenetstandard2.1 필하려는 경우

 public static class HashCode { public static int Combine(params object[] instances) { int hash = 17; foreach (var i in instances) { hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0)); } return hash; } }

struct 와 함께 사용하면 boxing으로 인해 메모리가 할당됩니다.


Ivan Sanz-Carasa

C++ Boost 라이브러리에서 접근 방식을 채택하려고 시도할 수 있습니다. 이 같은:

 class HashUtil { public static int HashCombine(int seed, int other) { unchecked { return other + 0x9e3779b9 + (seed << 6) + (seed >> 2); } } }

그리고:

 class MyClass { private string _field1; private int _field2; private AnotherClass _field3; private YetAnotherClass _field4; public override int GetHashCode() { int result = HashUtil.HashCombine(_field1.GetHashCode(), _field2); result = HashUtil.HashCombine(result, _field3.GetHashCode()); return HashUtil.HashCombine(result, _field4.GetHashCode()); } }

ivan.ukr

나는 내가 자주 돌아오는 이 스레드에 나의 최신 발견을 추가하고 싶습니다.

현재 Visual Studio/프로젝트 설정은 튜플을 구조체로 자동 리팩터링하는 기능을 제공합니다. 이렇게 하면 다음과 같은 GetHashCode 함수가 생성됩니다.

 public override int GetHashCode() { int hashCode = -2088324004; hashCode = hashCode * -1521134295 + AuftragGesperrt.GetHashCode(); hashCode = hashCode * -1521134295 + Auftrag_gesperrt_von.GetHashCode(); hashCode = hashCode * -1521134295 + Auftrag_gesperrt_am.GetHashCode(); return hashCode; }

t0b4cc0

출처 : http:www.stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-overriding-gethashcode

반응형