문제

.NET에서 System.Object.GetHashCode 메서드는 .NET 기본 클래스 라이브러리 전반에 걸쳐 많은 위치에서 사용됩니다.특히 컬렉션에서 항목을 빠르게 찾거나 동일성을 확인하려는 경우에는 더욱 그렇습니다.구현 방법에 대한 표준 알고리즘/모범 사례가 있습니까? GetHashCode 성능이 저하되지 않도록 사용자 정의 클래스를 재정의합니까?

도움이 되었습니까?

해결책

나는 보통 Josh Bloch의 구현과 같은 것을 가지고 간다 굉장한 효과적인 자바. 그것은 빠르며 충돌을 일으키지 않을 것 같지는 꽤 좋은 해시를 만듭니다. 예를 들어 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 대안은 비슷하게 간단하지만 상수를 사용합니다. XOR 대신에 ADD 결합 작업으로. 그것은 본다 무엇 아래 코드와 마찬가지로 일반 FNV 알고리즘은 개별 바이트에서 작동하므로 32 비트 해시 값 대신 바이트 당 하나의 반복을 수행하려면 수정해야합니다. 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를 다음과 같은 경우에만 무시해야합니다.

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

다른 팁

익명 유형

Microsoft는 이미 우수한 일반 해시 코드 생성기를 제공합니다. 속성/필드 값을 익명 유형으로 복사하고 해시입니다.

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

이것은 모든 속성에 대해 작동합니다. 복싱을 사용하지 않습니다. 익명 유형의 프레임 워크에서 이미 구현 된 알고리즘을 사용합니다.

Valuetuple- C# 7에 대한 업데이트

@cactuaroid가 주석에서 언급했듯이 값 튜플을 사용할 수 있습니다. 이렇게하면 몇 개의 키 스트로크가 절약되고 더 중요한 것은 스택에서 순전히 실행됩니다 (쓰레기 없음).

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

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

여기 내 해시 코드 도우미가 있습니다.
장점은 일반적인 유형 인수를 사용하므로 권투를 유발하지 않는다는 것입니다.

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

헬퍼 라이브러리에 해싱 클래스가 있어이 목적으로 사용합니다.

/// <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);
}

나는 그 성능을 평가하지 않았으므로 모든 피드백이 환영받습니다.

다음은 내 도우미 수업을 사용하고 있습니다 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 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);
}

Martin의 의견 후 업데이트 :

obj != null 권투가 발생하여 기본 비교로 전환했습니다.

  • 보다 이 답변 기본 비교 성능과 관련하여.
  • 보다 이 질문 널 값의 해시 코드에 대한 토론.

편집 (2018 년 5 월) :

EqualityComparer<T>.Default Getter는 이제 jit 내재적입니다 - 요청 요청 Stephen Toub에서 언급합니다 이 블로그 게시물.

equals ()가 여러 필드를 비교하는 대부분의 경우 gethash () 해시가 한 필드 또는 많은 부분에서 중요하지 않습니다. 해시를 계산하는 것이 정말 저렴한 지 확인해야합니다 (할당 없음, 제발) 그리고 빠른 (무거운 계산이 없습니다 그리고 확실히 데이터베이스 연결이 없음) 좋은 배포를 제공합니다.

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

그리고 마지막 팁 : gethashcode ()에 의존하지 마십시오.. 많은 .NET 유형은 재시작 후 해시 코드가 동일하게 유지되도록 보장하지 않으므로 메모리 데이터 구조에서 gethashCode ()의 값만 사용해야합니다.

최근까지 내 답변은 Jon Skeet의 답변과 매우 유사했습니다.그런데 최근에 2의 거듭제곱 해시 테이블, 즉 내부 테이블의 크기가 8, 16, 32 등인 해시 테이블을 사용하는 프로젝트를 시작했습니다.소수 크기를 선호하는 데에는 타당한 이유가 있지만 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까지 발생합니다.

해시 코드를 다시 혼합하면 해당 범위 전체에서 절대 충돌을 피하는 데 최소한 상당히 잘 수행된 해시 코드만 향상될 수 있습니다(232 가능한 값)이지만 해시 테이블에서 실제로 사용하기 위해 모듈로를 다운할 때 충돌을 피하는 데는 좋지 않습니다.2의 거듭제곱 테이블의 더 단순한 모듈로가 이를 더욱 분명하게 만들었지만, 이는 또한 보다 일반적인 소수 테이블에 부정적인 영향을 미쳤습니다. 이는 그다지 명확하지 않았습니다(재해싱의 추가 작업이 이점보다 더 클 것임) , 그러나 이점은 여전히 ​​존재합니다).

편집하다:나는 또한 개방형 주소 지정을 사용하고 있었는데, 이는 아마도 2의 거듭제곱이라는 사실보다 충돌에 대한 민감도를 더 높였을 것입니다.

그리고 글쎄요, 얼마나 충격적이었나요? string.GetHashCode() 구현 .그물 (또는 공부 여기)는 이 방법으로 개선될 수 있으며(충돌 횟수가 적기 때문에 테스트 실행 속도가 약 20~30배 더 빨라짐) 내 해시 코드가 얼마나 개선될 수 있는지(그보다 훨씬 더) 더 혼란스럽습니다.

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

그래서 저는 그 프로젝트를 한쪽으로 치워두고(어차피 아주 좋아하는 프로젝트였습니다) .NET에서 훌륭하고 잘 분산된 해시 코드를 빠르게 생성하는 방법을 찾기 시작했습니다.

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

이제 SpookyHash는 코드 조각을 기억하기가 쉽지 않습니다.더 나은 속도*를 위해 많은 부분을 직접 인라인했기 때문에 내 포트는 훨씬 적습니다.그러나 이것이 코드 재사용의 목적입니다.

그럼 내가 넣어 저것 원래 프로젝트에서 더 나은 해시 코드를 생성하는 방법에 대한 문제가 발생한 것처럼 해당 프로젝트에서도 더 나은 .NET memcpy를 생성하는 방법에 대한 문제가 발생했기 때문입니다.

그런 다음 돌아와서 거의 모든 기본 유형을 쉽게 제공하기 위해 많은 오버로드를 생성했습니다(예외 decimal†)를 해시 코드로 변환합니다.

속도가 빠르며, Bob Jenkins가 내가 이식한 원래 코드가 여전히 더 빠르기 때문에 대부분의 공로를 인정받을 자격이 있습니다. 특히 알고리즘이 최적화된 64비트 시스템에서는 더욱 그렇습니다‡.

전체 코드는 다음에서 볼 수 있습니다. 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();
}

또한 시드 값을 사용하므로 신뢰할 수 없는 입력을 처리해야 하고 해시 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는 64비트의 SpookyHash보다 상당히 빠릅니다. string.GetHashCode() 32비트에서는 약간 더 빠릅니다. string.GetHashCode() 64비트에서는 32비트의 SpookyHash보다 상당히 빠르지만 여전히 합리적인 선택이 될 만큼 충분히 빠릅니다.

이것은 좋은 것입니다 :

/// <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);
    }
}

다음은 또 다른 유창한 구현이 있습니다 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 일반적인 유형 제약으로 인해 클래스로 호출되지 않습니다. 그러나 컴파일러 지원은 없습니다 HashObject 일반적인 인수를 추가하기 때문에 복싱 작업도 추가됩니다.

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

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

이렇게하면 구현 세부 사항에 대해 걱정하지 않고도 품질 해시 코드가 생성됩니다.

여기 내 단순한 접근 방식이 있습니다. 나는 이것을 위해 클래식 빌더 패턴을 사용하고 있습니다. TypeSafe (권투/Unboxing 없음)이며 .NET 2.0 (확장 방법 없음)과 호환합니다.

다음과 같이 사용됩니다.

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

그리고 여기에 Acutal Builder 클래스가 있습니다.

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

resharper 사용자는 gethashcode, Equal 및 다른 사람을 생성 할 수 있습니다. ReSharper -> Edit -> Generate Code -> Equality Members.

// 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;
    }
}

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

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

원하는 경우 프라임을 올리는 것이 더 쉽다는 점을 제외하고 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;
        }
    }
}

우리가 8 개 이하의 속성이 없다면 (희망적으로) 여기에 또 다른 대안이 있습니다.

ValueTuple 구조물이며 고체가있는 것으로 보입니다 GetHashCode 구현.

그것은 우리가 단순히 이것을 할 수 있음을 의미합니다.

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

.NET Core의 현재 구현을 살펴 보겠습니다. ValueTuple'에스 GetHashCode.

이것은 왔습니다 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;
        }
    }

영어로:

  • 왼쪽으로 회전 (원형 이동) H1을 5 위치 씩 회전시킵니다.
  • 결과를 추가하고 H1을 함께 추가하십시오.
  • XOR H2와의 결과.
  • {static random seed, h1}에서 위의 작업을 수행하여 시작하십시오.
  • 각각의 추가 항목에 대해 이전 결과 및 다음 항목 (예 : H2)에서 작업을 수행하십시오.

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

유감스럽게도 연기 ValueTuple 우리 자신을 위해 GetHashCode 우리가 원하는만큼 빠르지 않을 수 있습니다. 이 의견 관련 토론에서는 직접 전화를 걸었습니다 HashHelpers.Combine 더 성능이 뛰어납니다. 반대로, 그 중 하나는 내부이므로 코드를 복사하여 여기에서 얻은 것의 많은 부분을 희생해야합니다. 또한 우리는 먼저 기억해야 할 책임이 있습니다 Combine 임의의 씨앗으로. 우리가 그 단계를 건너 뛰면 결과가 무엇인지 모르겠습니다.

.NET 코어 2.1 이상

.NET Core 2.1 이상을 사용하는 경우 System.HashCode 구조. 사용 방법에는 다음과 같습니다.

hashcode.combine

그만큼 Combine 방법을 사용하여 최대 8 개의 객체를 포기한 해시 코드를 작성할 수 있습니다.

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

hashcode.add

그만큼 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가 쉽게 만들었습니다'자세한 내용과 의견.

사용 예제

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;
    }
}

위의 답변으로 선택된 구현을 사용하여 플로트 및 소수의 문제를 해결했습니다.

이 테스트는 실패합니다 (플로트; 해시는 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));

그러나이 테스트는 (Ints와 함께) 통과합니다.

        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;
                }
        }
    }

이것은 Josh Bloch의 구현을 구현하는 정적 헬퍼 클래스입니다. 권투를 "방지"하고 긴 프리미티브를 위해 특별히 해시를 구현하기위한 명시적인 과부하를 제공합니다.

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

해시 출력은 항상 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;
        }
    }
}

여러 가지 해싱 방식에 대한 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(); 

여러 개의 큰 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;

그리고 다형도 마찬가지입니다. 모두 먼저 변환되었습니다 int 사용 GetHashCode()int 값이 xor'ed이고 결과는 해시입니다.

해시를 ID로 사용하는 사람들의 경우 (고유 한 값을 의미), 해시는 자연스럽게 여러 자리로 제한되며, 해싱 알고리즘의 경우 5 바이트, 최소한 MD5라고 생각합니다.

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top