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


1448

.NET에서 GetHashCode방법 는 .NET 기본 클래스 라이브러리의 여러 곳에서 사용됩니다. 컬렉션에서 항목을 빨리 찾거나 동등성을 결정할 때 올바르게 구현하는 것이 특히 중요합니다.

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


38
이 질문과 아래 기사를 읽은 후 재정의를 구현할 수 있습니다 GetHashCode. 다른 사람들에게 도움이되기를 바랍니다. Eric Lippert가 작성한 GetHashCode에 대한 지침 및 규칙
rene

4
"평등을 결정하기 위해": 아니오! 해시 코드가 동일한 두 객체가 반드시 같을 필요는 없습니다.
Thomas Levesque

1
@ThomasLevesque 맞습니다. 해시 코드가 같은 두 객체가 반드시 같을 필요는 없습니다. 그러나 여전히 GetHashCode()많은 구현에서 사용됩니다 Equals(). 그것이 제가 그 진술에서 의미 한 바입니다. GetHashCode()내부 Equals()는 종종 부등식 을 결정하는 지름길로 사용됩니다. 두 객체에 서로 다른 해시 코드가 있으면 동일하지 않은 객체 여야하고 나머지 동등성 검사를 실행할 필요가 없기 때문입니다.
bitbonk

3
@bitbonk 일반적으로, 둘 다 GetHashCode()Equals()두 객체의 모든 필드를 살펴 봐야합니다 (해시 코드가 같거나 확인되지 않은 경우이 작업을 수행해야 함). 이 때문에 GetHashCode()내부 통화 Equals()는 종종 중복되어 성능이 저하 될 수 있습니다. Equals()또한 회로를 단락시켜 훨씬 빠르게 만들 수 있지만 해시 코드가 캐시되어 GetHashCode()검사가 더 빠르고 가치가 있습니다. 자세한 내용은 이 질문 을 참조하십시오 .
NotEnoughData

2020 년 1 월 업데이트 : Eric Lippert의 블로그 : docs.microsoft.com/en-us/archive/blogs/ericlippert/…
Rick Davin

답변:


1603

나는 보통 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는 훌륭합니다 ... 그리고 작은 숫자로 본 대부분의 예제는 소수를 사용하는 경향이 있지만 비 프라임 숫자가 자주 사용되는 알고리즘은 적어도 유사합니다. 예를 들어, Fquit 가 아닌 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합성 동작한다. 그것은 보이는 뭔가 아래 코드 등이 있지만, 대신 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를 대체해야합니다.

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

8
언급 한 책에서 설명하는 알고리즘은 실제로 필드의 다른 데이터 유형에 대해 수행 할 작업을 especailly가 조금 더 자세히 설명합니다. 예 : 단순히 GetHashcode를 호출하는 대신 long 유형의 필드 (int) (field ^ f >>> 32)를 사용하십시오. long.GetHashCodes는 그런 식으로 구현됩니까?
bitbonk

13
예, Int64.GetHashCode는 정확히 그렇게합니다. 물론 박싱이 필요한 자바에서는. 그것은 저에게 상기시켜줍니다-책에 링크를 추가 할 시간입니다 ...
Jon Skeet

77
.net 3.5 SP1 현재) Dictionary<TKey,TValue>는 일부 분배 모듈로 특정 소수를 가정하기 때문에 23은 좋은 선택이 아닙니다 . 23은 그 중 하나입니다. 따라서 Capacity 23이있는 사전이있는 경우 마지막 GetHashCode해시 만 복합 해시 코드 에 영향을줍니다. 23 대신 29를 사용하고 싶습니다.
CodesInChaos

23
@CodeInChaos : 마지막 기여 만 버킷에 영향을 미치므로 최악의 경우 사전의 23 개 항목 을 모두 살펴 봐야 합니다. 여전히 각 항목의 실제 해시 코드를 확인하는 것이 저렴합니다. 작은 사전을 가지고 있다면 그다지 중요하지 않을 것입니다.
Jon Skeet

20
@ Vajda : 일반적으로 효과적인 해시 코드로 0을 사용 null합니다. 이는 필드를 무시하는 것과 다릅니다.
Jon Skeet

431

익명 유형

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

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

이것은 여러 속성에 적용됩니다. 권투를 사용하지 않습니다. 익명 형식의 프레임 워크에서 이미 구현 된 알고리즘 만 사용합니다.

ValueTuple-C # 7 업데이트

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

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

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


85
예, 익명 GetHashCode구현은 매우 효과적이지만 (BTW는 Jon Skeet의 답변과 동일하지만)이 솔루션의 유일한 문제는 모든 GetHashCode호출 에서 새 인스턴스를 생성한다는 것 입니다. 큰 해시 모음에 집중적으로 액세스하는 경우 특히 오버 헤드가
심할 수

5
@digEmAll 좋은 점은 새 객체를 만드는 오버 헤드에 대해서는 생각하지 않았습니다. Jon Skeet의 답변이 가장 효율적이며 권투를 사용하지 않습니다. (@Kumba VB에서 확인되지 않은 문제를 해결하려면 Int64 (long)를 사용하고 계산 후에 자릅니다.)
Rick Love

42
그냥 말할 수도 new { PropA, PropB, PropC, PropD }.GetHashCode()있습니다
sehe

17
VB.NET은 익명 형식 생성시 키를 사용해야합니다. New With {Key PropA}.GetHashCode()그렇지 않으면 GetHashCode는 동일한 '식별'속성을 가진 다른 개체에 대해 동일한 해시 코드를 반환하지 않습니다.
데이비드 오스본

4
이 경우 @Keith, 해시 코드가 계산 될 때마다 열거하는 대신 IEnumerable을 목록 값으로 저장하는 것이 좋습니다. GetHashCode 내부에서 매번 ToList를 계산하면 여러 상황에서 성능이 저하 될 수 있습니다.
Rick Love

105

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

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

5
T[]이미 따로 필요 없음IEnumerable<T>
nawfal

5
이러한 메소드를 리팩토링하고 핵심 로직을 하나의 함수로 제한 할 수 있습니다.
nawfal

12
또한, 31은 CPU의 시프트 및 뺄셈이며, 이는 매우 빠릅니다.
Chui Tey

4
@ nightcoder 매개 변수를 사용할 수 있습니다 .
ANeves

6
@ChuiTey 이것은 모든 Mersenne Prime 이 공통적으로 가지고있는 것입니다.
Pharap

63

이 목적으로 사용하는 도우미 라이브러리에 해싱 클래스가 있습니다.

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

성능을 평가하지 않았으므로 모든 의견을 환영합니다.


26
필드가 값 유형 인 경우 권투가 발생합니다.
nightcoder

5
"OverflowException을 포착하여 나중에 향상시킬 수 있습니다."의 요점 unchecked은에 필요한 오버 플로우에 대한 예외를 피하는 것입니다 GetHashCode. 따라서 값이 오버플로 int되고 전혀 아프지 않은 경우 올바르지 않습니다 .
Tim Schmelter

1
이 알고리즘의 한 가지 문제점은 널로 가득 찬 배열은 길이에 상관없이 항상 0을 리턴한다는 것입니다.
Nathan Adams

2
이 헬퍼 메소드는 새로운 객체도 할당합니다.
James Newton-King

1
@NathanAdams가 언급했듯이 null완전히 건너 뛴 사실 은 예기치 않은 결과를 줄 수 있습니다. 그것들을 건너 뛰는 대신 input[i].GetHashCode()when input[i]is null 대신 상수 값을 사용해야 합니다.
David Schwartz

58

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>.Defaultgetter는 이제 JIT 고유 기능 입니다. 이 블로그 게시물 에서 Stephen Toub가 풀 요청 을 언급합니다 .


1
3 차 운영자와의 노선을 다음과 같이 변경합니다.var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
Bill Barry

나는 삼항 연산자가 값 유형 인 경우 메모리를 할당 obj != null하는 box명령어로 컴파일 할 것이라고 믿습니다 T. 대신 메소드 obj.Equals(null)의 가상 호출로 컴파일 할 수 있습니다 Equals.
Martin Liversage

왜냐하면 this.hashCode != h. 동일한 값을 반환하지 않습니다.
Şafak Gür

죄송합니다. 댓글을 편집하지 말고 제거하십시오. 새 구조체를 만든 다음 hashCode를 읽기 전용이 아닌 것으로 변경하고 "확인되지 않은 {this.hashCode ^ = h * 397;} 이걸 반환하십시오." 예를 들어?
Erik Karlsson

불변성에는 장점이 있습니다 ( 왜 가변성 구조체가 악한가? ). 성능에 관해서는 힙에 공간을 할당하지 않기 때문에 내가하는 일은 상당히 저렴합니다.
Şafak Gür

30

.NET 표준 2.1 이상

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

HashCode.Combine

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

좋은 알고리즘은 무엇입니까?

속도

해시 코드를 계산하는 알고리즘은 빨라야합니다. 간단한 알고리즘은 일반적으로 더 빠를 것입니다.

결정론

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

충돌 감소

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

좋은 해시 함수는 예상 입력을 출력 범위에서 가능한 한 고르게 매핑해야합니다. 균일해야합니다.

예방의 DoS

.NET Core에서는 응용 프로그램을 다시 시작할 때마다 다른 해시 코드가 나타납니다. 이는 DoS (서비스 거부) 공격을 방지하기위한 보안 기능입니다. .NET Framework의 경우 다음 App.config 파일을 추가하여이 기능을 활성화 해야 합니다.

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

이 기능으로 인해 해시 코드는 작성된 응용 프로그램 도메인 외부에서 사용해서는 안되며 컬렉션에서 키 필드로 사용해서는 안되며 지속해서는 안됩니다.

이에 대한 자세한 내용은 여기를 참조 하십시오 .

암호화 적으로 안전한가?

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

  • 주어진 해시 값을 생성하는 메시지를 생성하는 것은 불가능합니다
  • 해시 값이 동일한 두 개의 다른 메시지를 찾는 것은 불가능합니다
  • 메시지를 조금만 변경하면 해시 값이 광범위하게 변경되어 새 해시 값이 이전 해시 값과 관련이없는 것으로 나타납니다 (애벌랜치 효과).

29

Equals ()가 여러 필드를 비교하는 대부분의 경우 GetHash ()가 한 필드 또는 여러 필드에서 해시되는지는 중요하지 않습니다. 해시 계산이 실제로 저렴하고 ( 할당 없음 , 제발) 빠르며 ( 무거운 계산 과 데이터베이스 연결이 없음) 확인해야합니다.

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

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


10
"Equals ()가 여러 필드를 비교하는 대부분의 경우 GetHash ()가 한 필드 또는 여러 필드에서 해시하는지는 중요하지 않습니다." 해시되지 않은 필드에서만 다른 객체의 경우 해시 충돌이 발생하기 때문에 이것은 위험한 조언입니다. 이 문제가 자주 발생하면 해시 기반 컬렉션 (HashMap, HashSet 등)의 성능이 저하됩니다 (최악의 경우 최대 O (n)).
sleske

10
이것은 실제로 자바에서 일어났다 : JDK의 초기 버전에서 String.hashCode ()는 문자열의 시작만을 고려했다; 이로 인해 끝에 만 다른 HashMaps에서 문자열을 키로 사용하는 경우 성능 문제가 발생합니다 (예 : URL에 공통). 따라서 알고리즘이 변경되었습니다 (JDK 1.2 또는 1.3에서는 믿습니다).
sleske

3
한 필드가 '좋은 분포를 제공'하면 (내 답변의 마지막 부분), 하나의 필드로 충분합니다. 좋은 분포를 제공하지 않으면 다른 계산이 필요합니다. (예는 다른 필드 사용 않는 좋은 분포를 제공하거나 여러 필드를 사용)
버트 Huijben

GetHashCode메모리 할당 을 수행 하는 데 문제가 있다고 생각하지 않습니다. 단지 처음 사용하는 경우에만 수행됩니다 (이후 호출은 단순히 캐시 된 결과를 반환합니다). 중요한 것은 충돌을 피하기 위해 많은 시간을 투자해야하는 것이 아니라 "체계적인"충돌을 피해야한다는 것입니다. 유형에 두 개의 int필드가 oldX있고 newX자주 다른 필드 가 있는 경우 해시 값은 해당 oldX^newX레코드의 90 %에 1, 2, 4 또는 8 의 해시 값을 할당합니다. oldX+newX[체크되지 않은 산술]을 사용하면 더 많은 충돌이 발생할 수 있습니다.
supercat

1
...보다 정교한 기능보다는, 각 해시 값에 두 개의 관련 항목이 있으면 하나의 해시 값에 500,001 개의 항목이 있고 다른 하나에 각각 하나가있는 경우 매우 나쁜 해시 값이 500,000 개의 1,000,000 개의 항목 모음이 매우 적합합니다.
supercat

23

최근까지 내 대답은 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로 변경되기 때문입니다.

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

편집 : 또한 개방 주소 지정을 사용하고 있었으며 충돌에 대한 민감도를 높였을 것입니다. 아마도 2의 거듭 제곱이었습니다.

그리고 .NETstring.GetHashCode()구현 (또는 여기 연구 ) 이이 방법으로 개선 될 수있는 정도를 방해하고 있었고 (충돌 횟수가 줄어들어 약 20-30 배 빠르게 실행되는 테스트 순서) 내 자신의 해시 코드의 양을 더 방해했습니다. (그보다 훨씬 더) 향상 될 수 있습니다.

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

그래서 나는 그 프로젝트를 한쪽에 놓았고 (어쨌든 애완 동물 프로젝트 였음) .NET에서 잘 분산 된 해시 코드를 신속하게 생성하는 방법을 찾기 시작했습니다.

결국 SpookyHash 를 .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();
}

또한 시드 값이 필요하므로 신뢰할 수없는 입력을 처리해야하고 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()64 비트 보다 약간 빠르며 32 비트의 SpookyHash보다 상당히 빠르지 만 여전히 합리적인 선택이 될 수 있습니다.


여러 해시 값을 하나로 결합 할 때 long중간 결과에 값 을 사용한 다음 최종 결과를로 낮추는 경향이 있습니다 int. 좋은 생각처럼 보입니까? 내 관심사는 hash = (hash * 31) + nextField를 사용하는 것입니다. 일치하는 값 쌍은 해시의 상위 27 비트에만 영향을 미칩니다. 계산을 확장하고 long물건을 포장하면 위험이 최소화됩니다.
supercat

@ supercat 그것은 당신의 최종 뭉크의 분포에 달려 있습니다. SpookilySharp 라이브러리는 blittable 유형에 대한 포인터를 전달하거나 직접 처리 할 수있는 열거 형 중 하나를 전달하여 배포가 양호하고 이상적으로 (객체 생성이 필요하지 않기 때문에) 확실하게 수행 할 수 있지만 아직 bllittable이없는 경우 데이터 또는 적절한 열거 형을 선택한 다음 .Update()위의 답변에 따라 여러 값으로 호출 하면 트릭을 수행합니다.
Jon Hanna

@JonHanna 문제가 발생한 문제를보다 정확하게 기꺼이 설명 하시겠습니까? 값 객체를 사소한 ( ValueUtils ) 구현하는 라이브러리를 구현하려고하는데 2의 제곱 해시 테이블에서 나쁜 해시 혼용 성을 보여주는 테스트 세트를 좋아합니다.
Eamon Nerbonne 2018 년

@EamonNerbonne 나는 "전체 시간이 그렇게 느려졌다"는 것보다 더 정확한 것을 가지고 있지 않습니다. 편집 내용을 추가하면서 공개 주소 지정을 사용하고 있다는 사실이 2의 제곱보다 중요했을 수 있습니다. 몇 가지 다른 접근 방식을 비교할 특정 프로젝트에서 일부 테스트 사례를 수행 할 계획이므로 우선 순위가 높지 않지만 (필요한 개인 프로젝트는 아니지만) 그 후에 더 나은 답변을 얻을 수 있습니다 , 그래서 나는 그것을 얻을 때 그것을 얻을 것이다 ...)
Jon Hanna

@ JonHanna : 예 개인 프로젝트 일정이 어떻게 진행되는지 알고 있습니다-행운을 빕니다! 어쨌든, 나는 마지막 주석을 잘 표현하지 않았다는 것을 알았습니다 : 나는 문제의 입력을 요구하고 그 결과로 발생한 문제의 세부 사항은 아닙니다. 테스트 세트 (또는 테스트 세트에 대한 영감)로 사용하고 싶습니다. 어쨌든 애완 동물 프로젝트와 함께 행운을 빕니다 :-).
Eamon Nerbonne 2016 년

13

이것은 좋은 것입니다 :

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

1
키는 어떻게 결정됩니까? GetHashCode ()는 매개 변수를 사용하지 않으므로 어떻게 든 결정 해야하는 두 개의 키로이 키를 호출해야합니다. 추가 설명이 없으면 영리하게 보이지만 그렇게 좋지는 않습니다.
Michael Stum

일반 오버로드가 필요한 이유는 무엇입니까? 모든 객체에는 GetHashCode()메소드가 있으므로 유형은 중요하지 않으며 코드에서 사용되지 않습니다 . 따라서 항상 메소드를 paramsarray 매개 변수 와 함께 사용할 수 있습니다 . 아니면 여기에 뭔가 빠졌습니까?
gehho

4
제네릭 대신 객체를 사용하면 GetHashCode에서 원하지 않는 권투 및 메모리 할당이 제공됩니다. 그래서 제네릭은 갈 길입니다.
코드 InChaos

1
후행 쉬프트 / h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);엑소 스텝 ( 코드 냄새가 나기 : 입력에 의존하지 않고 나에게 너무 중복되어 보인다)
sehe

1
@Magnus 예, 원래 의견을 삭제하겠습니다. 이 방법은 다른 솔루션만큼 빠르지는 않지만 중요하지는 않습니다. 배포판은 대부분의 솔루션보다 우수하므로 나에게서 +1합니다! :)
nawfal

11

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

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

그러면 구현 세부 정보에 대해 걱정할 필요없이 품질 해시 코드가 생성됩니다.


그것은 달콤한 추가처럼 보입니다 ... 어떤 버전의 .NET Core가 들어올 지 알 수있는 방법이 있습니까?
Dan J

1
@DanJ 정말 우연의 일치로, HashCodecorefx에 대한 변경 사항은 몇 시간 전에 귀하의 의견이 병합되었습니다.
James Ko

정말 대단한 시간입니다. 공감. :)
Dan J

@DanJ 더 좋은 소식은 닷넷 코어 MyGet 피드에서 호스팅되는 CoreFX의 야간 빌드에서 지금 사용할 수 있다는 것입니다.
James Ko

우리가 꽤있어 이후, 직장에서 나에게 도움이되지 않습니다 - 달콤한 것을 출혈 첨단하지만, 잘 알고. 건배!
Dan J

9

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일반 인수를 추가하면 권투 작업이 추가되므로 컴파일러가 지원되지 않습니다 .


8

여기에 간단한 접근 방식이 있습니다. 이를 위해 클래식 빌더 패턴을 사용하고 있습니다. 형식이 안전하고 (박싱 / 언 박싱 없음) .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);
        }
    }
}

Mangus의 답변 에서처럼 gethashcode 함수 내에서 객체 생성을 피할 수 있습니다. 망할 정적 해시 함수 (스타터 해시를 걱정하는)를 호출하십시오. 또한 AddItems<T>(params T[] items)헬퍼 클래스에서 AddItem(T)매번 호출하는 것보다 메소드를 더 자주 사용할 수 있습니다 .
nawfal

this.result * Prime2 * item.GetHashCode()자주 사용 하면 어떤 이점 이 this.result * Prime2 + item.GetHashCode()있습니까?
nawfal

AddItems<T>(params T[] items)때문에 더 자주 사용할 수 없습니다 typeof(T1) != typeof(T2).
bitbonk

아 그래.
nawfal

5

ReSharper 사용자는로 GetHashCode, Equals 등을 생성 할 수 있습니다 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;
    }
}

4

우리가 8 개 이하의 속성을 가지고 있다면 (여기서) 다른 대안이 있습니다.

ValueTuple구조체이며 견고한 GetHashCode구현으로 보입니다 .

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

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

하자가에 대한 .NET 코어의 현재 구현을 살펴 가지고 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을 더합니다.
  • h2로 결과를 XOR하십시오.
  • {정적 랜덤 시드, h1}에서 위 작업을 수행하여 시작하십시오.
  • 각 추가 항목에 대해 이전 결과 및 다음 항목 (예 : h2)에 대한 작업을 수행하십시오.

이 ROL-5 해시 코드 알고리즘의 특성에 대해 자세히 알고 있으면 좋을 것입니다.

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


h1 >> 27무시 한다고 0 이라고 가정하면 h1 << 5같으 h1 * 32므로 와 같습니다 h1 * 33 ^ h2. 이 페이지 에 따르면 "Modified Bernstein"이라고합니다.
cactuaroid

3

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

// Unique ID from database
private int _id;

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

즉, Person 및 Account 객체가 있고 ID와 1이 모두 같으면 동일한 해시 코드를 갖게됩니다. 그리고 그것은 좋지 않습니다.
pero

15
실제로 위의 주석이 잘못되었습니다. 해시 코드 충돌 가능성은 항상 있습니다 (해시 코드는 개별 객체가 아닌 버킷 만 찾습니다). 따라서 혼합 객체를 포함하는 해시 코드에 대한 이러한 구현은 많은 충돌을 유발할 수 있지만 바람직하지는 않지만 해시 테이블에 단일 유형의 객체 만 가지고 있다면 절대 괜찮습니다. 또한 균등하게 배포되지는 않지만 system.object의 기본 구현도 아니므로 너무 걱정하지 않아도됩니다 ...
piers7

2
id는 정수이므로 해시 코드는 단지 id 일 수 있습니다. 정수에 대해 GetHashCode를 호출 할 필요가 없습니다 (ID 함수 임)
Darrel Lee

2
@DarrelLee 그러나 그의 _id는 tomo가 될 수 있습니다. _id.GetHashCode의도가 분명하기 때문에 코딩하는 것이 좋습니다 .
nawfal

2
@ 1224는 사용 패턴에 따라 제공하는 이유로 끔찍할 수 있지만 훌륭 할 수도 있습니다. 구멍이없는 일련의 숫자가 있다면 어떤 알고리즘보다 완벽한 해시를 얻을 수 있습니다. 그것이 사실이라면 당신은 그것을 믿고 평등 검사를 건너 뛸 수 있습니다.
Jon Hanna

3

원한다면 프라임을 올리는 것이 더 쉽다는 점을 제외하면 나이트 코더의 솔루션과 거의 비슷합니다.

추신 : 이것은 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;
        }
    }
}

2
널을 처리하지 않습니다.
JJS

1

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

이 테스트는 실패합니다 (부동; 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));

그러나이 테스트는 정수와 함께 통과합니다.

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

1
경우 그렇지 않으면 구성 unchecked에 영향을주지 않습니다 Convert.ToInt32: uint, long, float, double그리고 decimal여기에 모든 오버 플로우가 있습니다.
Mark Hurd

1

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 값 을 변환 하고 결과는 해시입니다.

해시를 ID로 사용하는 사람들 (독특한 값을 의미 함)의 경우 해시는 자연적으로 여러 자릿수로 제한됩니다. 해시 알고리즘의 경우 최소 5 바이트, MD5 이상이라고 생각합니다.

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


7
해시 코드를 만들기 위해 정수를 Xoring하면 잘 알려진 반 패턴으로 실제 값과 특히 많은 충돌이 발생하는 경향이 있습니다.
Jon Hanna

여기에있는 모든 사람은 정수를 사용하며 해시가 동일하다는 보장은 없었습니다. 충돌이 거의 발생하지 않는 한 다양하게 변경하려고했습니다.
deadManN

예, 그러나 두 번째와 다섯 번째는 충돌을 피하려고 시도하지 않습니다.
존 한나

1
그렇습니다. 그 반 패턴은 매우 일반적입니다.
존 한나

2
도달해야 할 균형이 있습니다. Spookyhash와 같은 정말 좋은 해시 코드를 사용하면 훨씬 더 나은 충돌 방지 효과를 얻을 수 있지만 이들 중 어느 것보다 훨씬 많은 계산 시간이 있습니다 (그러나 대량의 데이터 해싱과 관련하여 Spookyhash는 매우 빠릅니다). xoring 이전의 값 중 하나에 대한 간단한 이동은 충돌의 좋은 감소를위한 약간의 추가 비용입니다. 소수와 곱셈은 시간과 품질을 다시 증가시킵니다. 따라서 교대 또는 복수 사이에서 더 나은 것은 논쟁의 여지가 있습니다. 평범한 xor는 실제 데이터와 충돌하는 경우가 많으며 피하는 것이 가장 좋습니다.
Jon Hanna

1

이것은 Josh Bloch의 구현을 구현하는 정적 헬퍼 클래스입니다. 그리고 권투를 "예방"하고 긴 기본 요소를 위해 해시를 구현하기 위해 명시적인 과부하를 제공합니다.

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

해시 출력은 항상 정수이므로 해시 호출을 연결하면됩니다.

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

Yipes : 나는 버그를 발견했다! HashKeysAndValues방법은 수정되었습니다 : 그것은 호출합니다 HashKeyAndValue.
Steven Coco

0

혹시 polyfill 원하는 HashCode에서netstandard2.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하면 권투로 인해 메모리가 할당됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.