GetHashCode'u geçersiz kılmak için en iyi algoritma nedir?


1448

.NET'te GetHashCodeyöntem , .NET temel sınıf kitaplıkları boyunca birçok yerde kullanılır. Düzgün bir şekilde uygulanması, bir koleksiyonda veya eşitliği belirlerken öğeleri hızlı bir şekilde bulmak özellikle önemlidir.

GetHashCodePerformansımı düşürmemek için özel sınıflarım için nasıl uygulanacağı konusunda standart bir algoritma veya en iyi uygulama var mı?


38
Bu soruyu ve aşağıdaki makaleyi okuduktan sonra geçersiz kılmayı uygulayabilirim GetHashCode. Umarım başkaları için yararlı olur. Eric Lippert tarafından yazılmış GetHashCode için kurallar ve kurallar
rene

4
"veya eşitliği belirlemek için": hayır! Aynı hash koduna sahip iki nesne mutlaka eşit değildir.
Thomas Levesque

1
@ThomasLevesque Haklısınız, aynı karma koduna sahip iki nesne mutlaka eşit değildir. Ama yine GetHashCode()de pek çok uygulamasında kullanılmaktadır Equals(). Bu ifadeyle kastettiğim buydu. GetHashCode()içeride Equals()genellikle eşitsizliği belirlemek için bir kısayol olarak kullanılır , çünkü iki nesnenin farklı bir karma kodu varsa, bunlar eşit olmayan nesneler olmalı ve eşitlik denetiminin geri kalanının yürütülmesi gerekmez.
bitbonk

3
@bitbonk Genellikle, her ikisi de GetHashCode()ve Equals()her iki nesnenin tüm alanlarına bakmanız gerekir (Eşittir, hashcode'lar eşit veya işaretlenmemişse bunu yapmak zorundadır). Bu nedenle, GetHashCode()içerideki bir çağrı Equals()genellikle gereksizdir ve performansı düşürebilir. Equals()kısa devre yapabilir ve çok daha hızlı hale getirebilir - ancak bazı durumlarda karma kodlar önbelleğe alınabilir, bu da GetHashCode()kontrolü daha hızlı ve değerli kılar. Daha fazla bilgi için bu soruya bakın .
NotEnoughData

Yanıtlar:


1603

Genellikle Josh Bloch'un muhteşem Etkili Java'sında verilen uygulama gibi bir şeyle giderim . Hızlıdır ve çarpışmalara neden olması muhtemel olmayan oldukça iyi bir karma oluşturur. İki farklı asal sayı seçin, örneğin 17 ve 23 ve şunları yapın:

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

Yorumlarda belirtildiği gibi, bununla çarpmak için büyük bir asal seçmenin daha iyi olduğunu görebilirsiniz. Görünüşe göre 486187739 iyi ... ve küçük sayılarla gördüğüm çoğu örnek asal kullanma eğiliminde olmasına rağmen, asal olmayan sayıların sıklıkla kullanıldığı en azından benzer algoritmalar var. Oldukça FNV örneğinde daha sonra, örneğin, görünüşe göre iyi çalışan sayılar kullandım - ancak başlangıç ​​değeri asal değil. (Çarpma sabit olan asal olsa. Oldukça ne kadar önemli olduğunu bilmiyorum.)

Bu, XORiki ana nedenden dolayı hashcode'ların ortak uygulamasından daha iyidir . İki intalana sahip bir türümüz olduğunu varsayalım :

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

Bu arada, önceki algoritma C # derleyicisi tarafından anonim türler için şu anda kullanılan algoritmadır.

Bu sayfa oldukça fazla seçenek sunuyor. Çoğu durumda yukarıdaki "yeterince iyi" ve hatırlamak ve doğru almak inanılmaz derecede kolay olduğunu düşünüyorum. FNV'nin alternatif benzer basit olmakla birlikte, farklı sabitler ve kullanır XORyerine ADDbir birleşme işlemi gibi. Görünüşe şey aşağıdaki kodu gibi ama bu yerine 32-bit hash değeri başına, bayt başına bir kez yinelenir gerçekleştirmek için modifiye gerektirecektir yüzden normal bir FNV algoritması, bireysel bayt üzerinde çalışır. FNV aynı zamanda değişken uzunluktaki veriler için de tasarlanırken, burada kullanma şeklimiz her zaman aynı sayıda alan değeri içindir. Bu yanıta yapılan yorumlar, buradaki kodun, yukarıdaki ekleme yaklaşımı olarak gerçekten de (test edilen örnek durumda) çalışmadığını göstermektedir.

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

Dikkat edilmesi gereken bir nokta, ideal olarak, eşitlik duyarlı (ve böylece hashcode duyarlı) durumunuzun hash koduna bağlı bir koleksiyona ekledikten sonra değişmesini önlemeniz gerektiğidir.

Gereğince belgeler :

Değişmez referans türleri için GetHashCode'u geçersiz kılabilirsiniz. Genel olarak, değiştirilebilir referans türleri için GetHashCode'u yalnızca aşağıdaki durumlarda geçersiz kılmalısınız:

  • Değişken olmayan alanlardan karma kodunu hesaplayabilirsiniz; veya
  • Nesne, karma koduna dayanan bir koleksiyonun içindeyken, değiştirilebilir bir nesnenin karma kodunun değişmemesini sağlayabilirsiniz.

8
Bahsettiğiniz kitapta açıklanan algoritma, alanların farklı veri türleri için ne yapılacağını biraz daha ayrıntılı bir şekilde etkilemektedir. Örneğin: GetHashcode'u çağırmak yerine uzun kullanım (int) (alan ^ f >>> 32) alanları için. Long.GetHashCodes bu şekilde uygulandı mı?
bitbonk

13
Evet, Int64.GetHashCode tam olarak bunu yapıyor. Java da boks gerektirir elbette. Bu bana hatırlatıyor - kitaba bir bağlantı ekleme zamanı ...
Jon Skeet

77
(.Net 3.5 SP1'den itibaren) Dictionary<TKey,TValue>bazı primerlerin iyi dağılım modülasyonunu üstlendiği için 23 iyi bir seçim değildir . Ve 23 de onlardan biri. Dolayısıyla, Kapasite 23 ile bir sözlüğünüz varsa GetHashCode, bileşik karma kodunu yalnızca son katkıda bulunur . Bu yüzden 23 yerine 29 kullanmayı tercih ederim.
CodesInChaos

23
@CodeInChaos: Yalnızca son katkı kovayı etkiler - bu nedenle en kötü ihtimalle sözlükteki 23 girişin tümünü incelemek zorunda kalabilir . Hala ucuz olacak her girişin gerçek karma kodunu kontrol edecek. Eğer bu kadar küçük bir sözlüğünüz varsa, çok önemli değildir.
Jon Skeet

20
@Vajda: Genellikle 0 değerini etkin yoklama kodu olarak kullanıyorum null- bu, alanı yok saymakla aynı şey değil.
Jon Skeet

431

Anonim Tür

Microsoft zaten iyi bir genel HashCode üreteci sağlar: Özellik / alan değerlerinizi anonim bir türe kopyalayın ve hash yapın:

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

Bu, herhangi bir sayıda özellik için çalışacaktır. Boks kullanmaz. Sadece anonim türler için zaten çerçevede uygulanan algoritmayı kullanır.

ValueTuple - C # 7 Güncellemesi

Yorumlarda @cactuaroid'den bahsedildiği gibi, bir değer grubu kullanılabilir. Bu, birkaç tuş vuruşunu kaydeder ve daha da önemlisi yalnızca yığın üzerinde yürütür (Çöp yok):

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

(Not: Anonim türleri kullanan orijinal teknik, yığın üzerinde bir nesne, yani çöp gibi bir nesne oluşturuyor gibi görünmektedir, çünkü anonim türler sınıf olarak uygulanmaktadır, ancak bu derleyici tarafından optimize edilebilir. Bu seçenekleri karşılaştırmak ilginç olacaktır, ancak tuple seçeneği daha üstün olmalıdır.)


85
Evet, anonim GetHashCodeuygulama çok etkilidir (BTW, Jon Skeet'in cevabı ile aynıdır), ancak bu çözümle ilgili tek sorun, herhangi bir GetHashCodeçağrıda yeni bir örnek oluşturmanızdır . Özellikle büyük karma koleksiyonlara yoğun erişim durumunda biraz havai olabilir ...
digEmAll

5
@digEmAll İyi bir nokta, yeni bir nesne yaratma yükünü düşünmedim. Jon Skeet'in cevabı en verimli ve boks kullanmayacak. (@Kumba VB'deki kontrolsüzleri çözmek için, bir Int64 (uzun) kullanın ve hesaplamalardan sonra kısaltın.)
Rick Love

42
Sadece söyleyebiliriz new { PropA, PropB, PropC, PropD }.GetHashCode()çok
sehe

17
VB.NET, anonim tür oluşturmada Anahtar kullanmalıdır: New With {Key PropA}.GetHashCode()Aksi takdirde GetHashCode, aynı 'tanımlama' özelliklerine sahip farklı nesneler için aynı karma kodu döndürmez.
David Osborne

4
@ Bu durumda, IEnumerable'ı her bir hashcode hesaplandığında numaralandırma yerine bir liste değeri olarak kaydetmeyi düşünürdüm. GetHashCode içinde her seferinde ToList hesaplamak birçok durumda performansa zarar verebilir.
Rick Love

105

İşte benim hashcode helper.
Avantajı, genel tür argümanları kullanması ve bu nedenle boksa neden olmamasıdır:

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

Ayrıca akıcı bir arayüz sağlamak için uzatma yöntemine sahiptir, böylece bunu şöyle kullanabilirsiniz:

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

ya da bunun gibi:

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

5
T[]Zaten olduğu gibi ayrı ayrı gerek yokIEnumerable<T>
nawfal

5
Bu yöntemleri yeniden düzenleyebilir ve çekirdek mantığı bir işlevle kısıtlayabilirsiniz
nawfal

12
Bu arada, 31, CPU üzerinde son derece hızlı olan bir kaydırma ve çıkarma işlemidir.
Chui Tey

4
@nightcoder params kullanabilirsiniz .
ANeves

6
@ChuiTey Bu, Mersenne Primes'in ortak noktasıdır .
Pharap

63

Bu amaçla kullandığım Yardımcı kütüphanesinde bir Hashing sınıfım var.

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

Sonra, sadece şu şekilde kullanabilirsiniz:

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

Performansını değerlendirmedim, bu yüzden herhangi bir geri bildirim memnuniyetle karşılandı.


26
Alanlar değer türleri ise, boksa neden olur.
nightcoder

5
"OverflowException özel durumu yakalanarak daha sonra geliştirilebilir" Tüm uncheckedamacı, taşma üzerinde istenen özel durumlardan kaçınmaktır GetHashCode. Bu yüzden değer taşarsa intve hiç zarar vermezse yanlış değildir.
Tim Schmelter

1
Bu algoritma ile bir sorunu deliklerle dolu herhangi dizisi hep 's uzunluğu ne olursa olsun, 0 dönecektir olmasıdır
Nathan Adams

2
Bu yardımcı yöntem ayrıca yeni bir nesne tahsis eder []
James Newton-King

1
@NathanAdams'ın belirttiği gibi, nulltamamen atlanan gerçek size beklenmedik sonuçlar verebilir. Bunları atlamak yerine , null input[i].GetHashCode()olduğunda değil, yalnızca sabit bir değer kullanmalısınız input[i].
David Schwartz

58

Jon Skeet'in uygulamasını kullanan yardımcı sınıfım .

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

Kullanımı:

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

System.Int32 için bir uzantı yöntemi yazmaktan kaçınmak istiyorsanız:

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

Yine de herhangi bir yığın tahsisini önler ve tam olarak aynı şekilde kullanılır:

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

Edit (Mayıs 2018): Getter EqualityComparer<T>.Defaultartık bir JIT içsel - çekme isteği Stephen Toub tarafından bu blog yazısında belirtildi .


1
Üçüncül operatör ile çizgiyi şu şekilde değiştirirdim:var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
Bill Barry

Üçlü operatör ile bir değer türü ise bellek ayıracak obj != nullbir boxtalimat derlemek inanıyorum T. Bunun yerine obj.Equals(null), Equalsyöntemin sanal çağrısına derlenecek olanı kullanabilirsiniz .
Martin Liversage

Çünkü this.hashCode != h. Aynı değeri döndürmez.
Şafak Gür

Maalesef, yorumumu düzenlemek yerine kaldırmayı başarın. Yeni bir yapı oluşturmak daha sonra hashCode'u salt okunur olarak değiştirmek ve şunu yapmak daha yararlı mıdır: "unchecked {this.hashCode ^ = h * 397;} bunu döndür;" Örneğin?
Erik Karlsson

Değişmezliğin faydaları vardır (Değişebilir yapılar neden kötülüktür? ). Performans hakkında, yaptığım şey yığınta yer ayırmadığı için oldukça ucuz.
Şafak Gür

30

.NET Standard 2.1 ve Üstü

.NET Standard 2.1 veya üstünü kullanıyorsanız, System.HashCode yapısını kullanabilirsiniz . Bunu kullanmanın iki yöntemi vardır:

HashCode.Combine

CombineYöntem, bir karma kodu oluşturmak için kullanılan sekiz nesnelere kadar verilebilir.

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

HashCode.Add

AddYöntem koleksiyonları ile başa çıkmak için yardımcı olur:

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 Kolaylaştı

Daha fazla bilgi ve yorum için ' GetHashCode Made Easy ' blog yazısının tamamını okuyabilirsiniz .

Kullanım Örneği

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

uygulama

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

İyi Bir Algoritma Nedir?

hız

Bir karma kodu hesaplayan algoritmanın hızlı olması gerekir. Basit bir algoritma genellikle daha hızlı bir algoritma olacaktır.

deterministik

Karma algoritma deterministik olmalıdır, yani aynı girdi göz önüne alındığında daima aynı çıktıyı üretmelidir.

Çarpışmaları Azaltın

Bir karma kodunu hesaplayan algoritmanın karma çarpışmalarını minumum olarak tutması gerekir . Karma çarpışma, GetHashCodeiki farklı nesneye yapılan iki çağrı özdeş karma kodlar ürettiğinde oluşan bir durumdur . Çarpışmalara izin verildiğini (bazılarının olmadıkları yanılgıları olduğunu) unutmayın, ancak asgari düzeyde tutulmalıdır.

İyi bir sağlama fonksiyonu, beklenen girişleri çıkış aralığında olabildiğince eşit olarak eşleştirmelidir. Tekdüzelik olmalı.

Prevent's DoS

.NET Core'da bir uygulamayı her başlattığınızda farklı karma kodlar alırsınız. Bu, Hizmet Reddi saldırılarını (DoS) önlemek için bir güvenlik özelliğidir. .NET Framework için, gereken aşağıdaki App.config dosyası ekleyerek bu özelliği etkinleştirmek:

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

Bu özellik nedeniyle, karma kodları hiçbir zaman oluşturuldukları uygulama alanının dışında kullanılmamalı, hiçbir zaman bir koleksiyondaki anahtar alanlar olarak kullanılmamalı ve asla kalıcı olmamalıdır.

Bununla ilgili daha fazla bilgiyi buradan edinebilirsiniz .

Kriptografik Olarak Güvenli mi?

Algoritmanın bir Şifreleme karma işlevi olması gerekmez . Yani aşağıdaki koşulları yerine getirmek zorunda değildir:

  • Belirli bir karma değeri veren bir mesaj oluşturmak olanaksızdır
  • Aynı hash değerine sahip iki farklı mesaj bulmak mümkün değil
  • Mesajda yapılan küçük bir değişiklik, karma değerini o kadar büyük ölçüde değiştirmelidir ki, yeni karma değeri eski karma değeri (çığ etkisi) ile ilgisiz görünecektir.

29

Equals () 'ın birden çok alanı karşılaştırdığı çoğu durumda GetHash ()' inizin bir alanda mı yoksa birçok alanda mı olduğu önemli değildir. Sadece karma hesaplamak gerçekten ucuz ( tahsis yok , lütfen) ve hızlı ( ağır hesaplamalar ve kesinlikle hiçbir veritabanı bağlantısı yok) ve iyi bir dağıtım sağlar emin olun.

Ağır kaldırma Equals () yönteminin bir parçası olmalıdır; sağlama, mümkün olduğunca az öğe üzerinde Eşittir () çağrılmasını mümkün kılmak için çok ucuz bir işlem olmalıdır.

Ve son bir ipucu: GetHashCode () uygulamasının birden fazla uygulama çalıştırmasında kararlı olmasına güvenmeyin . Birçok .Net türü, yeniden başlatma işleminden sonra karma kodlarının aynı kalmasını garanti etmez, bu nedenle bellek veri yapılarında yalnızca GetHashCode () değerini kullanmalısınız.


10
"Equals () 'ın birden çok alanı karşılaştırdığı çoğu durumda GetHash ()' inizin bir alanda mı yoksa birçok alanda mı olması önemli değildir." Bu tehlikeli bir tavsiye, çünkü sadece karma olmayan alanlarda farklılık gösteren nesneler için karma çarpışmalar alacaksınız. Bu sık sık gerçekleşirse, karma tabanlı koleksiyonların (HashMap, HashSet vb.) Performansı düşer (en kötü durumda O (n'ye kadar).
sleske

10
Bu aslında Java'da oldu: JDK String.hashCode () 'un ilk sürümlerinde yalnızca dizenin başlangıcı sayılır; bu, yalnızca sonunda farklılık gösteren (ör. URL'ler için ortak olan) HashMaps'te Dizeler anahtar olarak kullandıysanız performans sorunlarına yol açar. Bu nedenle algoritma değiştirildi (JDK 1.2 veya 1.3'te inanıyorum).
sleske

3
Eğer bu bir alan 'iyi bir dağıtım sağlıyorsa (cevabımın son kısmı), o zaman bir alan yeterlidir .. Eğer iyi bir dağıtım sağlamazsa , (ve tam o zaman) başka bir hesaplamaya ihtiyacınız vardır. (Örn sadece başka alanı kullanabilirsiniz mu iyi dağılımını sağlamak, ya da birden çok alan kullanın)
Bert Huijben

Sadece ilk kez kullanması koşuluylaGetHashCode bellek ayırmaları gerçekleştirmeyle ilgili bir sorun olduğunu düşünmüyorum (sonraki çağrılar sadece önbelleğe alınmış bir sonuç döndürüyor). Önemli olan, çarpışmalardan kaçınmak için büyük çaba sarf etmemesi değil, "sistemik" çarpışmalardan kaçınmasıdır. Bir türün iki alanı varsa ve sık sık bir alan farklıysa , bu tür kayıtların% 90'lık bir karma değeri 1, 2, 4 veya 8 hash değerlerini atayacaktır. [İşaretlenmemiş aritmetik] kullanmak daha fazla çarpışma oluşturabilir ...intoldXnewXoldX^newXoldX+newX
supercat

1
... daha karmaşık bir işleve göre, ancak 500.000 farklı hash değeri olan 1.000.000 şeyden oluşan bir koleksiyon, her bir karma değerinin iki ilişkili şeyi varsa ve bir karma değerinin 500.001 ve diğerlerinin her birine sahip olması durumunda çok kötü olacaktır.
supercat

23

Yakın zamana kadar cevabım Jon Skeet'e çok yakın olacaktı. Ancak, son zamanlarda iki güç tablosunu kullanan, yani iç tablonun büyüklüğünün 8, 16, 32, vb. Olduğu karma tabloları kullanan bir projeye başladım. Asal sayı boyutlarını tercih etmek için iyi bir neden var, ama orada iki boyutun gücüne de bazı avantajlar sağlar.

Ve hemen hemen emdi. Biraz deneme ve araştırma yaptıktan sonra hashlerimi yeniden hash etmeye başladım:

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

Ve sonra iki kişilik karma masam artık emmedi.

Bu beni rahatsız etti, çünkü yukarıdakiler işe yaramamalı. Ya da daha doğrusu, orijinal GetHashCode()çok belirli bir şekilde fakir değilse, çalışmamalıdır .

Bir karma kodun yeniden karıştırılması büyük bir karma kodu iyileştiremez, çünkü olası tek etki birkaç çarpışma daha getirmemizdir.

Bir karma kodunun yeniden karıştırılması korkunç bir karma kodunu iyileştiremez, çünkü olası tek etki örneğin 53 değeri üzerindeki çok sayıda çarpışmayı çok sayıda 18.3487.291 değerine değiştirmektir.

Bir karma kodunun yeniden karıştırılması, aralığı boyunca mutlak çarpışmalardan kaçınmak için en azından oldukça iyi bir karma kodunu iyileştirebilir (2 32 olası değer), ancak bir karma tablosunda gerçek kullanım için modüle edildiğinde çarpışmalardan kaçınmak için kötü bir şekilde. İki güçten oluşan bir tablonun daha basit modulou bunu daha belirgin hale getirirken, daha yaygın olan asal sayı tablolarıyla da olumsuz bir etkisi vardı, ki bu çok açık değildi (yeniden şekillendirmedeki ekstra çalışma faydadan daha ağır basacaktı , ancak fayda hala orada olurdu).

Düzenleme: Ben de çarpışma duyarlılığını artıracak açık adresleme kullanıyordum, belki de iki güç olduğu gerçeğinden daha fazla.

Ve de, ne kadar rahatsız oldu string.GetHashCode()uygulamalarda .NET (veya çalışma burada (nedeniyle daha az çarpışmaları daha hızlı 20-30 kere çalışan testlerin sipariş üzerine) bu şekilde geliştirilebilir) ve daha ne kadar kendi karma kodlar rahatsız geliştirilebilir (bundan çok daha fazlası).

Geçmişte kodladığım ve aslında bu sitedeki cevapların temeli olarak kullandığım tüm GetHashCode () uygulamaları, düşündüğümden çok daha kötüydü . Çoğu zaman bu kullanımlar için "yeterince iyi", ama daha iyi bir şey istedim.

Bu yüzden bu projeyi bir tarafa koydum (yine de bir evcil hayvan projesiydi) ve hızlı bir şekilde .NET'te iyi, iyi dağıtılmış bir karma kodun nasıl üretileceğini araştırmaya başladım.

Sonunda SpookyHash'i .NET'e taşımaya karar verdim . Aslında yukarıdaki kod, 32 bit girişten 32 bit çıkış üretmek için SpookyHash kullanmanın hızlı yollu bir sürümüdür.

Şimdi, SpookyHash bir kod parçasını hatırlamak hoş bir hızlı değil. Bağlantı noktam daha da azdır, çünkü daha iyi hız için çok fazla elle satır çizdim *. Ancak bu kodun yeniden kullanımı içindir.

Sonra bu projeyi bir tarafa koydum , çünkü orijinal projenin nasıl daha iyi bir karma kod üretileceği sorusunu ürettiği gibi, bu proje daha iyi bir .NET memcpy'nin nasıl üretileceği sorusunu üretti.

Sonra geri döndüm ve hemen hemen tüm yerli türleri ( decimal† hariç ) bir karma koduna kolayca beslemek için aşırı yükler ürettim.

Bob Jenkins kredinin çoğunu hak ettiği için hızlı, çünkü taşıdığım orijinal kodu daha hızlı, özellikle de algoritmanın ‡ için optimize edildiği 64 bit makinelerde daha hızlı.

Kodun tamamı https://bitbucket.org/JonHanna/spookilysharp/src adresinde görülebilir, ancak yukarıdaki kodun basitleştirilmiş bir versiyonu olduğunu düşünün.

Ancak, daha önce yazıldığı için bundan daha kolay yararlanılabilir:

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

Ayrıca tohum değerleri de alır, bu nedenle güvenilmeyen girdilerle uğraşmanız ve Hash DoS saldırılarına karşı korunmanız gerekiyorsa, çalışma süresine veya benzerine dayalı bir tohum ayarlayabilir ve sonuçları saldırganlar tarafından öngörülemez hale getirebilirsiniz:

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

* Buradaki büyük sürpriz, (x << n) | (x >> -n)iyileştirilmiş şeyleri döndüren bir rotasyon yönteminin elle yerleştirilmesidir . Jitterin bunu benim için sıraya koyacağından emin olurdum, ancak profilleme aksini gösterdi.

decimal, C # 'dan olmasına rağmen .NET açısından yerel değildir. Sorun şu ki, kendi GetHashCode()hassasiyeti önemli olarak dikkate alırken, kendi başına duyarlı Equals()değildir. Her ikisi de geçerli seçimlerdir, ancak böyle karıştırılmamıştır. Kendi sürümünüzü uygularken, birini veya diğerini seçmeniz gerekir, ancak hangisini istediğinizi bilemiyorum.

Comparison Karşılaştırma yoluyla. Bir dize üzerinde kullanılırsa, 64 bit üzerindeki SpookyHash, string.GetHashCode()32 bit üzerinde biraz daha hızlıdır string.GetHashCode(), bu da 64 bitten biraz daha hızlıdır , bu da 32 bit üzerindeki SpookyHash'ten oldukça hızlıdır, ancak yine de makul bir seçim olacak kadar hızlıdır.


Birden çok karma değerini bir araya longgetirirken, ara sonuçlar için değerleri kullanma eğilimindeyim ve daha sonra nihai sonucu bir değerine düşürürüm int. Bu iyi bir fikir gibi mi görünüyor? Benim endişem, örneğin hash = (hash * 31) + nextField kullanmasıdır, o zaman eşleşen değer çiftleri sadece karma'nın üst 27 bitini etkiler. Hesaplamanın a'ya girmesine izin vermek longve bir şeyleri sarmak bu tehlikeyi en aza indirir.
supercat

@supercat son munging'inizin dağıtımına bağlıdır. SpookilySharp kütüphanesi dağıtımın iyi olmasını sağlar, ideal olarak (nesne oluşturmaya ihtiyaç duymayacağı için) bir işaretçiyi blittable türüne geçirerek veya doğrudan işlediği numaralandırılanlardan birini geçirerek, ancak zaten blittable'ınız yoksa veri veya uygun bir numaralandırma, daha sonra .Update()yukarıdaki cevaba göre birden fazla değerle çağrı yapmak hile yapacaktır.
Jon Hanna

@JonHanna, karşılaştığınız sorunlu davranışta daha kesin olmak ister misiniz? Değer nesneleri uygulama önemsiz ( ValueUtils ) yapan bir kütüphane uygulamaya çalışıyorum ve iki karma karma güç karma karmaşasını gösteren bir test seti isterim.
Eamon Nerbonne

@EamonNerbonne Gerçekten "genel zaman bu şekilde daha yavaş oldu" daha kesin bir şey yok. Bir düzenlemeye eklediğim gibi, açık adresleme kullandığım iki faktörün gücünden daha önemli olabilirdi. Birkaç farklı yaklaşımı karşılaştıracağım belirli bir projede bazı test senaryoları yapmayı planlıyorum, bu yüzden bundan sonra sizin için daha iyi bir yanıt alabilirim, ancak bu yüksek öncelikli değil (acil ihtiyaç duymayan kişisel bir proje) , bu yüzden ben aldığımda ben hallederim ...)
Jon Hanna

@JonHanna: evet kişisel proje programının nasıl geçtiğini biliyorum - iyi şanslar! Her halükarda, son yorumu iyi ifade etmediğimi görüyorum: Sorunlu girdiyi sormak istedim ve sonuçta ortaya çıkan sorunların ayrıntılarını istemiyorum. Bunu bir test seti (veya bir test seti için ilham kaynağı) olarak kullanmak isterim. Her durumda - evcil hayvan projenizde iyi şanslar :-).
Eamon Nerbonne

13

Bu iyi bir tane:

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

Ve işte nasıl kullanılacağı:

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
Anahtarlar nasıl belirlenir? GetHashCode () herhangi bir parametre almaz, bu nedenle bunu bir şekilde belirlenmesi gereken iki Anahtarla çağırması gerekir. Üzgünüm, daha fazla açıklama yapmadan bu sadece akıllı görünüyor, ama o kadar iyi değil.
Michael Stum

Ve neden genel aşırı yüklemelere ihtiyacınız var? Tüm nesnelerin bir GetHashCode()yöntemi olduğu için tür önemli değildir (ve kodunuzda kullanılmaz) , bu nedenle yöntemi her zaman paramsdizi parametresiyle kullanabilirsiniz. Yoksa burada bir şey mi kaçırıyorum?
Gehho

4
Jenerikler yerine nesne kullandığınızda GetHashCode'da istemediğiniz boks ve bellek ayırmalarını alırsınız. Yani jenerikler gitmenin yolu.
CodesInChaos

1
Sondaki vardiya / xor adımlar ( h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);a codesmell vardır: bunlar giriş herhangi bağlı olmayan ve müthiş bana yedekli bak.
sehe

1
@Magnus evet doğru, orijinal yorumumu sileceğim. Bunun diğer bazı çözümler kadar hızlı olmayabileceğini, ancak dediğin gibi önemli olmadığını unutmayın. Dağıtım harika, buradaki çoğu çözümden daha iyi, bu yüzden +1 benden! :)
nawfal

11

Https://github.com/dotnet/coreclr/pull/14863 itibariyle , çok basit olan karma kodlar oluşturmanın yeni bir yolu var! Sadece yaz

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

Bu, uygulama ayrıntıları hakkında endişelenmenize gerek kalmadan kaliteli bir karma kodu oluşturur.


Bu tatlı bir ek gibi görünüyor ... hangi .NET Core hangi sürümü gemi olacak bilmek için herhangi bir yolu?
Dan J

1
@DanJ Ne mutlu bir tesadüf, HashCodeyorumunuzdan sadece birkaç saat önce corefx için yapılan değişiklikler birleştirildi :) Tipin .NET Core 2.1'de gönderilmesi planlanıyor.
James Ko

Bu harika - ve oldukça geri dönüş süresi. Upvoted. :)
Dan J

@DanJ Daha da iyi haberler - şu anda dotnet çekirdekli MyGet yayınında barındırılan her gece CoreFX yapılarında mevcut olmalıdır.
James Ko

Tatlı - işte bana yardım etmiyor, çünkü biz o kadar kanamayız, ama iyi biliyoruz. Şerefe!
Dan J

9

Yukarıda, Jon Skeet tarafından yayınlanan , ancak hiçbir tahsis veya boks işlemi içermeyen algoritmanın akıcı bir uygulaması :

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

Kullanımı:

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

Derleyici HashValue, genel tür kısıtlaması nedeniyle bir sınıfla çağrılmayacaktır. Ancak, HashObjectgenel bir argüman eklemek de bir boks işlemi eklediğinden derleyici desteği yoktur .


8

İşte benim basit yaklaşımım. Bunun için klasik oluşturucu kalıbını kullanıyorum. Typesafe (boks / kutulama yok) ve .NET 2.0 (uzantı yöntemi vb.)

Bu şekilde kullanılır:

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

Ve burada acutal oluşturucu sınıfı:

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'un cevabındaki gibi gethashcode işlevi içinde nesne oluşturmayı önleyebilirsiniz. Sadece lanet statik hash fonksiyonlarını çağırınız (marş hashına önem veren) Ayrıca, AddItems<T>(params T[] items)yöntemi yardımcı sınıfta daha sık kullanabilirsiniz ( AddItem(T)her seferinde çağırmak yerine ).
nawfal

this.result * Prime2 * item.GetHashCode()Sıkça kullanıldığında ne gibi bir fayda elde ediyorsunuz this.result * Prime2 + item.GetHashCode()?
nawfal

Ben kullanamaz AddItems<T>(params T[] items)daha sık çünkü typeof(T1) != typeof(T2)vs.
bitbonk

oh evet bunu kaçırdım.
nawfal

5

ReSharper kullanıcıları GetHashCode, Equals ve diğerlerini birlikte üretebilir 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'den fazla mülkümüz yoksa (umarım), işte başka bir alternatif.

ValueTuple bir yapıdır ve sağlam GetHashCode uygulamaya .

Bu, basitçe bunu yapabileceğimiz anlamına gelir:

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

Diyelim için .NET Core geçerli uygulama bakmak ValueTuple's GetHashCode.

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

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

İngilizcede:

  • Sola döndürme (dairesel kaydırma) h1, 5 konum.
  • Sonucu ve h1'i birlikte ekleyin.
  • X2 ile sonuç XOR.
  • Yukarıdaki işlemi {static random seed, h1} üzerinde gerçekleştirerek başlayın.
  • Diğer her öğe için, önceki sonuç ve sonraki öğe (örn. H2) üzerinde işlemi gerçekleştirin.

Bu ROL-5 karma kod algoritmasının özellikleri hakkında daha fazla bilgi edinmek güzel olurdu.

Ne yazık ki, ValueTuplekendimiz için ertelemek GetHashCodeistediğimiz ve beklediğimiz kadar hızlı olmayabilir. İlgili bir tartışmadaki bu yorum , doğrudan aramanın HashHelpers.Combinedaha performanslı olduğunu göstermektedir. Flip tarafında, bu bir iç, bu yüzden burada kopyaladıklarımızın çoğunu feda ederek, kodu kopyalamak zorundayız. Ayrıca, ilk önce Combinerastgele tohumla hatırlamaktan sorumlu oluruz . Bu adımı atlarsak sonuçların ne olduğunu bilmiyorum.


Varsayarsak h1 >> 27, bunu göz ardı etmek 0'dır h1 << 5eşittir h1 * 32o aynıdır bu nedenle h1 * 33 ^ h2. Bu sayfaya göre , "Modifiye Bernstein" olarak adlandırılmaktadır.
cactuaroid

3

İşlerimin çoğu veritabanı bağlantısıyla yapılır, bu da sınıflarımın veritabanından benzersiz bir tanımlayıcıya sahip olduğu anlamına gelir. Ben her zaman hashcode oluşturmak için veritabanından kimliği kullanın.

// Unique ID from database
private int _id;

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

Bu, Kişi ve Hesap nesneleriniz varsa ve her ikisinde de ve ID = 1 varsa, aynı karma koduna sahip olacakları anlamına gelir. Ve bu doğru değil.
pero

15
Aslında yukarıdaki yorum yanlış. Her zaman karma kodu çarpışma olasılığı vardır (karma kodu tek tek nesneyi değil, yalnızca grubu bulur). Bu nedenle, karışık nesneler içeren bir karma kod için böyle bir uygulama, çok sayıda çarpışmaya yol açacaktır, bu istenmeyen bir durumdur, ancak hashtables'larınızda tek bir tür nesneye sahip olmanız kesinlikle iyi olacaktır. Ayrıca eşit olarak dağıtılmaz, ancak ne de
system.object

2
Kimlik bir tamsayı olduğu için karma kodu sadece id olabilir. GetHashCode'u tamsayı olarak adlandırmaya gerek yoktur (bu bir kimlik fonksiyonudur)
Darrel Lee

2
@DarrelLee ama onun kimliğini tomo bir Rehber olabilir. _id.GetHashCodeNiyet açık olduğu için bunu yapmak iyi bir kodlama uygulamasıdır .
nawfal

2
@ 1224 kullanım şekillerine bağlı olarak verdiğiniz nedenden dolayı korkunç olabilir, ama aynı zamanda harika olabilir; deliksiz bu tür bir sayı diziniz varsa, herhangi bir algoritmanın üretebileceğinden daha iyi bir hash vardır. Durumun bu olduğunu biliyorsanız, buna güvenebilir ve eşitlik kontrolünü atlayabilirsiniz.
Jon Hanna

3

İsterseniz primer yetiştirmek daha kolay olduğu için nightcoder'ın çözümüne oldukça benzer.

Not: Bu, 9 varsayılanın tek bir yönteme dönüştürülebileceğini bilerek ağzınızda biraz kusacağınız zamanlardan biridir, ancak daha yavaş olacaktır, bu yüzden gözlerinizi kapatıp unutmaya çalışın.

/// <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
Boş değerlerle başa çıkmaz.
JJS

1

Yukarıdaki yanıt olarak seçilen uygulamayı kullanarak kayan sayı ve ondalık sayılarla ilgili bir sorunla karşılaştım.

Bu test başarısız olur (yüzer; karma 2 değeri negatif olarak değiştirdiğim halde aynıdır):

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

Ancak bu test geçer (ints ile):

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

İlkel türler için GetHashCode kullanmamak için uygulamamı değiştirdim ve daha iyi çalışıyor gibi görünüyor

    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
Eğer aksi amaçlanan uncheckedETKİLEMEZ Convert.ToInt32: uint, long, float, doubleve decimalburada tüm taşma olabilir.
Mark Hurd

1

Microsoft hash çeşitli yolu için kurşun ...

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

Birden fazla büyük int için bunu kullanabilirsiniz tahmin edebilirsiniz:

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;

Ve çok türü için aynı: tüm ilk dönüştürülen intkullanarakGetHashCode() int değerleri xor'ed ve sonuç sizin karma edilecektir sonra.

Karma kimliği olarak kullananlar için (benzersiz bir değer anlamına gelir), karma doğal olarak birkaç basamakla sınırlıdır, sanırım karma algoritması için 5 bayt, en azından MD5.

Birden çok değeri karma bir değere dönüştürebilirsiniz ve bazıları aynıdır, bu yüzden tanımlayıcı olarak kullanmayın. (belki bir gün bileşeninizi kullanacağım)


7
Bir hash kodu yapmak için tamsayıları küçültmek, gerçek dünya değerlerine sahip özellikle çok sayıda çarpışma ile sonuçlanma eğilimi gösteren iyi bilinen bir antipatterndir.
Jon Hanna

Buradaki her biri tamsayı kullanır ve hashın aynı olacağına dair hiçbir garanti yoktur, sadece çok az çarpışma olduğu kadar değişken olmaya çalışmıştır.
deadManN

Evet, ama ikinciniz ve beşinciniz çarpışmalardan kaçınmaya çalışmaz.
Jon Hanna

1
Evet, bu antipattern oldukça yaygın.
Jon Hanna

2
Ulaşmak için bir denge var. Spookyhash gibi gerçekten iyi bir karma kodu kullanın ve çok, çok daha iyi çarpışma önleme elde edersiniz, ancak bunlardan herhangi birinden çok daha fazla hesaplama süresi olacaktır (ancak çok büyük miktarda veri hash etmeye gelince, Spookyhash son derece hızlıdır). Xoring öncesi değerlerden birinde basit bir değişim, çarpışmada iyi bir azalma için sadece marjinal ekstra maliyettir. Asal sayı çarpımı hem zamanı hem de kaliteyi tekrar arttırır. Vardiya veya mult arasında daha iyi olan bu nedenle tartışmalıdır. Düz xor olsa da çok sık gerçek veriler üzerinde çok sayıda çarpışma vardır ve en iyi kaçınılması
Jon Hanna

1

Bu, Josh Bloch'un uygulanmasını uygulayan statik bir yardımcı sınıftır; ve boksu "önlemek" ve ayrıca hash'i özellikle uzun ilkellere uygulamak için açık aşırı yüklemeler sağlar.

Eşit uygulamanızla eşleşen bir dize karşılaştırması iletebilirsiniz.

Hash çıktısı her zaman bir int olduğundan, Hash çağrılarını zincirleyebilirsiniz.

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: Bir hata buldum! HashKeysAndValuesYöntem, giderilmiştir: bu çağırır HashKeyAndValue.
Steven Coco

0

Eğer Polyfill istediğiniz HashCodedannetstandard2.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;
    }
}

Not: Birlikte kullanılırsa struct, boks nedeniyle bellek ayırır

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.