C # GetHashCode Kuralları


136

Essential C # 3.0 ve .NET 3.5 kitabında şunları okudum:

GetHashCode () öğesinin belirli bir nesnenin ömrü boyunca geri dönüşü, nesnenin verileri değişse bile sabit (aynı değer) olmalıdır. Çoğu durumda, bunu uygulamak için yöntem dönüşünü önbelleğe almalısınız.

Bu geçerli bir rehber mi?

.NET'te birkaç yerleşik türü denedim ve böyle davranmadılar.


Mümkünse, kabul edilen cevabı değiştirmeyi düşünebilirsiniz.
Giffyguy

Yanıtlar:


93

Cevap çoğunlukla geçerli bir kılavuzdur, ancak belki de geçerli bir kural değildir. Ayrıca tüm hikayeyi anlatmaz.

Belirtilen nokta, değiştirilebilir türler için karma kodunu değiştirilebilir verilere dayandıramayacağınızdır, çünkü iki eşit nesne aynı karma kodunu döndürmelidir ve karma kod, nesnenin ömrü boyunca geçerli olmalıdır. Karma kodu değişirse, artık doğru karma kutusunda bulunmadığından karma koleksiyonunda kaybolan bir nesne ile sonuçlanırsınız.

Örneğin, A nesnesi karma 1 değerini döndürür. Bu nedenle, karma tablosunun 1. bölmesine gider. Daha sonra A nesnesini 2 karma değerini döndürecek şekilde değiştirirsiniz. Bir karma tablosu aramaya gittiğinde, 2. bölmeye bakar ve bulamaz - nesne 1. bölmede yetim kalmıştır. nesnenin ömrü boyunca değişmez ve GetHashCode uygulamalarını yazmanın sadece bir nedeni popoda bir acıdır.

Güncelleme
Eric Lippert hakkında mükemmel bilgi veren bir blog yayınladıGetHashCode .

Ek Güncelleme
Yukarıda birkaç değişiklik yaptım:

  1. Kural ve kural arasında bir ayrım yaptım.
  2. "Nesnenin ömrü boyunca" yaşadım.

Kılavuz kural değil, sadece bir kılavuzdur. Gerçekte, GetHashCodebu yönergeleri yalnızca nesnenin bir karma tabloda saklandığı gibi yönergeleri izlemesini beklediğinde izlemesi gerekir. Nesnelerinizi hiçbir zaman karma tablolarında (veya kurallarına dayanan başka bir şeyde) kullanmak istemiyorsanız GetHashCode, uygulamanızın yönergeleri izlemesi gerekmez.

"Nesnenin ömrü boyunca" ifadesini gördüğünüzde, "nesnenin karma tablolarıyla işbirliği yapması gereken süre boyunca" veya benzerlerini okumalısınız. Çoğu şey gibi, GetHashCodekuralları ne zaman kıracağınızı bilmekle ilgilidir.


1
Değişken tipler arasındaki eşitliği nasıl belirliyorsunuz?
Jon B

9
Eşitliği belirlemek için GetHashCode kullanmamalısınız.
JSB ձոգչ

4
@JS Bangs - MSDN'den: GetHashCode'u geçersiz kılan türetilmiş sınıflar, eşit kabul edilen iki nesnenin aynı karma koduna sahip olmasını sağlamak için Eşittir'i de geçersiz kılmalıdır; aksi takdirde, Hashtable türü düzgün çalışmayabilir.
Jon B

3
@Joan Venge: İki şey. Birincisi, Microsoft bile her uygulamada GetHashCode'a sahip değil. İkincisi, değer türleri genellikle mevcut bir örneğin değiştirilmesinden ziyade her değerin yeni bir örnek olmasıyla değişmez.
Jeff Yates

17
A.Equals (b), a.GetHashCode () == b.GetHashCode () anlamına geldiği için, eşitlik karşılaştırması için kullanılan veriler değiştirildiğinde çoğu zaman karma kodunun değiştirilmesi gerekir. Sorunun değişebilir verilere dayanan GetHashCode olmadığını söyleyebilirim. Sorun muh nesnelerini karma tablo anahtarları olarak kullanmak (ve aslında onları mutasyona uğratmak). Yanlış mıyım?
Niklas

120

Uzun zaman oldu, ancak yine de neden bu soruya doğru cevaplar vermenin gerekli olduğunu düşünüyorum. Şimdiye kadarki en iyi cevap, MSDN'yi kapsamlı bir şekilde alıntılayan cevaptır - kendi kurallarınızı yapmaya çalışmayın, MS adamları ne yaptıklarını biliyordu.

Ama önce ilk şeyler: Soruda belirtildiği gibi Kılavuz yanlıştır.

Şimdi nedenler - ikisi var

Birincisi : Eğer hashcode bir şekilde hesaplanırsa, nesnenin kendisi değişse bile, nesnenin ömrü boyunca değişmezse eşittir-kontratını kıracaktır.

Unutmayın: "İki nesne eşit olarak karşılaştırılırsa, her nesne için GetHashCode yöntemi aynı değeri döndürmelidir. Ancak, iki nesne eşit olarak karşılaştırılmazsa, iki nesne için GetHashCode yöntemlerinin farklı değerler döndürmesi gerekmez."

İkinci cümle genellikle "Tek kural, nesne oluşturma zamanında eşit nesnelerin karma kodunun eşit olması" şeklinde yanlış yorumlanır. Nedenini gerçekten bilmiyorum, ama bu burada da çoğu cevabın özü ile ilgili.

Eşit yöntemde adın kullanıldığı bir ad içeren iki nesne düşünün: Aynı ad -> aynı şey. Örnek A Oluşturun: Adı = Joe Örnek B Oluşturun: Adı = Peter

Hashcode A ve Hashcode B büyük olasılıkla aynı olmayacaktır. Şimdi B örneğinin adı Joe olarak değiştirildiğinde ne olur?

Sorudaki yönergelere göre, B'nin karma kodu değişmez. Bunun sonucu şöyle olacaktır: A.Equals (B) ==> true Ama aynı zamanda: A.GetHashCode () == B.GetHashCode () ==> false.

Ancak tam olarak bu davranış eşittir & hashcode-contract tarafından yasaklanmıştır.

İkinci neden : - Tabii ki - doğru olsa da, karma koddaki değişiklikler karma listeleri ve karma kodu kullanan diğer nesneleri bozabilir, bunun tersi de doğrudur. En kötü durumda karma kodun değiştirilmemesi, birçok farklı nesnenin hepsinin aynı karma koduna sahip olacağı ve aynı karma bölmesinde bulunacağı karma listeler alır - nesneler standart bir değerle başlatıldığında olur.


Şimdi nasıl oluyor? İlk bakışta bir çelişki var gibi görünüyor - her iki durumda da kod kırılacak. Ancak her iki sorun da değişen veya değişmeyen karma koddan gelmez.

Sorunların kaynağı MSDN'de iyi tanımlanmıştır:

MSDN'nin hashtable girişinden:

Anahtar nesneler, Hashtable'da anahtar olarak kullanıldığı sürece değişmez olmalıdır.

Bu şu anlama gelir:

Bir hashvalue oluşturan herhangi bir nesne, nesne değiştiğinde hashvalue değerini değiştirmelidir, ancak bir Hashtable (veya elbette başka bir Hash kullanan nesne) içinde kullanıldığında, kesinlikle herhangi bir değişikliğe izin vermemelidir. .

İlk olarak en kolay yol, elbette, sadece gerektiğinde normal, değişebilir nesnelerin kopyaları olarak oluşturulacak hashtable'larda kullanım için değişmez nesneleri tasarlamaktır. Değişmez nesnelerin içinde, değişmez olduğu için hash kodunu önbelleğe almak zor bir şekilde uygundur.

İkincisi Ya da nesneyi "şimdi hashediniz" olarak belirtin, tüm nesne verilerinin gizli olduğundan emin olun, nesne verilerini değiştirebilecek tüm işlevlerde bayrağı kontrol edin ve değişikliğe izin verilmiyorsa bir istisna verisi atayın (yani bayrak ayarlanmışsa) ). Şimdi, nesneyi herhangi bir karma alana koyduğunuzda, artık gerekli olmadığında bayrağı ayarladığınızdan ve - bayrağı da kaldırdığınızdan emin olun. Kullanım kolaylığı için, bayrağı "GetHashCode" yöntemi içinde otomatik olarak ayarlamanızı öneririm - bu şekilde unutulamaz. Ve bir "ResetHashFlag" yönteminin açık çağrısı, programcının nesne verilerini şu anda değiştirmesine izin verilip verilmeyeceğini düşünmek zorunda kalacaktır.

Tamam, ne söylenmeli: Eşit veriler ve hashcode-contract'ı ihlal etmeden, değiştirilebilen verileri olan nesnelerin, yine de hash kodunun değişmediği durumlar vardır.

Ancak bu, eşittir yönteminin değişebilir verilere dayanmamasını gerektirir. Yani, bir nesne yazıp bir değeri yalnızca bir kez hesaplayan ve daha sonra yapılan çağrılarda döndürmek için nesne içinde saklayan bir GetHashCode yöntemi oluşturursam, o zaman, tekrar kullanmalıyım: A.Equals (B) hiçbir zaman yanlıştan doğruya değişmeyecek şekilde karşılaştırılır. Aksi takdirde sözleşme bozulur. Bunun sonucu genellikle Eşittir yönteminin herhangi bir anlam ifade etmemesi olacaktır - orijinal referans eşit değildir, ancak her ikisi de eşit değildir. Bazen, bu amaçlanan davranış (yani müşteri kayıtları) olabilir, ancak genellikle değildir.

Bu nedenle, nesne verileri değiştiğinde GetHashCode sonucunun değişmesini sağlayın ve nesnelerin listelerin veya nesnelerin kullanılmasıyla karma içinde kullanılması amaçlanıyorsa (veya sadece mümkünse), nesneyi değiştirilemez hale getirin veya kullanmak için salt okunur bir bayrak oluşturun nesneyi içeren karma listenin ömrü.

(Bu arada: Tüm bunlar C # veya .NET'e özgü değildir - nesne listeleyken, nesnelerin verilerini tanımlamanın asla değiştirilmemesi gereken tüm karma uygulamaların veya daha genel olarak herhangi bir dizinlenmiş listenin doğasındadır. Bu kural bozulursa, beklenmedik ve öngörülemeyen davranışlar ortaya çıkar. Bir yerde, liste içindeki tüm öğeleri izleyen ve listeyi otomatik olarak yeniden dizine ekleyen liste uygulamaları olabilir - ancak bunların performansı en iyi ihtimalle kesinlikle korkunç olacaktır.)


23
Bu ayrıntılı açıklama için +1 (yapabilirsem daha fazlasını verir)
Oliver

5
+1, bu ayrıntılı açıklama nedeniyle kesinlikle daha iyi cevap! :)
Joe

9

Gönderen MSDN

İki nesne eşit olarak karşılaştırılırsa, her nesne için GetHashCode yöntemi aynı değeri döndürmelidir. Ancak, iki nesne eşit olarak karşılaştırılmazsa, iki nesne için GetHashCode yöntemlerinin farklı değerler döndürmesi gerekmez.

Nesne için GetHashCode yöntemi, nesnenin Eşittir yönteminin dönüş değerini belirleyen nesne durumunda herhangi bir değişiklik olmadığı sürece tutarlı olarak aynı karma kodunu döndürmelidir. Bunun yalnızca bir uygulamanın geçerli yürütülmesi için geçerli olduğunu ve uygulama yeniden çalıştırılırsa farklı bir karma kodun döndürülebileceğini unutmayın.

En iyi performans için, bir karma işlevinin tüm girdiler için rasgele bir dağıtım oluşturması gerekir.

Bu, nesnenin değerleri değiştiğinde, karma kodunun değişmesi gerektiği anlamına gelir. Örneğin, "Ad" özelliği "Tom" olarak ayarlanmış bir "Kişi" sınıfında bir karma kodu ve adı "Jerry" olarak değiştirirseniz farklı bir kod olmalıdır. Aksi halde, Tom == Jerry.


Düzenle :

Ayrıca MSDN'den:

Eşit kabul edilen iki nesnenin aynı karma koduna sahip olmasını sağlamak için GetHashCode'u geçersiz kılan türetilmiş sınıflar da Equals'ı geçersiz kılmalıdır; aksi takdirde, Hashtable türü düzgün çalışmayabilir.

Gönderen MSDN'ın hashtable girişi :

Anahtar nesneler, Hashtable'da anahtar olarak kullanıldığı sürece değişmez olmalıdır.

Bunu okuma şeklim, bir hashtable içinde kullanılmak üzere tasarlanmadığı sürece , değişken nesnelerin değerleri değiştikçe farklı hashcode'ları döndürmesi gerektiğidir .

System.Drawing.Point örneğinde, nesne kesilebilir ve yapar farklı karma kodunun X ya da Y değeri değişir döndürür. Bu, hashtable'da olduğu gibi kullanılmasını zayıf bir aday yapar.


GetHashCode (), bir karma tabloda kullanılmak üzere tasarlanmıştır, bu işlevin tek noktası budur.
skolima

@skolima - MSDN belgeleri bununla tutarsız. Değişken nesneler GetHashCode () uygulayabilir ve nesnenin değeri değiştikçe farklı değerler döndürmelidir. Hashtables değişken anahtarlar kullanmalıdır. Bu nedenle, GethashCode () yöntemini, karma değeri dışında bir şey için kullanabilirsiniz.
Jon B

9

GetHashcode ile ilgili belgelerin biraz kafa karıştırıcı olduğunu düşünüyorum.

Bir yandan, MSDN bir nesnenin hashcode'unun hiçbir zaman değişmemesi ve sabit olması gerektiğini belirtir. Öte yandan MSDN, bu 2 nesnenin eşit olduğu düşünülürse GetHashcode'un dönüş değerinin 2 nesne için eşit olması gerektiğini belirtir.

MSDN:

Bir karma işlevi aşağıdaki özelliklere sahip olmalıdır:

  • İki nesne eşit olarak karşılaştırılırsa, her nesne için GetHashCode yöntemi aynı değeri döndürmelidir. Ancak, iki nesne eşit olarak karşılaştırılmazsa, iki nesne için GetHashCode yöntemlerinin farklı değerler döndürmesi gerekmez.
  • Nesne için GetHashCode yöntemi, nesnenin Eşittir yönteminin dönüş değerini belirleyen nesne durumunda herhangi bir değişiklik olmadığı sürece tutarlı olarak aynı karma kodunu döndürmelidir. Bunun yalnızca bir uygulamanın geçerli yürütülmesi için geçerli olduğunu ve uygulama yeniden çalıştırılırsa farklı bir karma kodun döndürülebileceğini unutmayın.
  • En iyi performans için, bir karma işlevinin tüm girdiler için rasgele bir dağıtım oluşturması gerekir.

Daha sonra bu, tüm nesnelerinizin değişmez olması gerektiği veya GetHashcode yönteminin, değişkeninizin değiştirilemeyen özelliklerine dayandırılması gerektiği anlamına gelir. Örneğin bu sınıfa sahip olduğunuzu varsayalım (naif uygulama):

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

Bu uygulama, MSDN'de bulunabilecek kuralları zaten ihlal ediyor. Bu sınıfın 2 örneğine sahip olduğunuzu varsayalım; instance1 öğesinin Name özelliği 'Pol' olarak ayarlanır ve example2'nin Name özelliği 'Piet' olarak ayarlanır. Her iki örnek de farklı bir karma kodu döndürür ve eşit değildir. Şimdi, example2'nin adını 'Pol' olarak değiştirdiğimi varsayalım, o zaman, Eşittir yöntemime göre, her iki örnek de eşit olmalı ve MSDN kurallarından birine göre, aynı karma kodu döndürmelidir.
Ancak, bu yapılamaz, çünkü örnek2 karma kodu değişecektir ve MSDN buna izin verilmediğini belirtmektedir.

Daha sonra, bir varlığınız varsa, belki de ideal olarak bir yedek anahtar veya değişmez bir özellik olan o varlığın 'birincil tanımlayıcısını' kullanması için hash kodunu uygulayabilirsiniz. Bir değer nesneniz varsa, Hashcode'u bu değer nesnesinin 'özelliklerini' kullanacak şekilde uygulayabilirsiniz. Bu özellikler değer nesnesinin 'tanımını' oluşturur. Bu elbette bir değer nesnesinin doğasıdır; onun kimliğiyle değil, onun değeriyle ilgileniyorsunuz.
Bu nedenle, değer nesneleri değişmez olmalıdır. (Tıpkı .NET çerçevesinde oldukları gibi, dize, Tarih, vb ... hepsi değişmez nesnelerdir).

Akla gelen başka bir şey:
Hangi 'oturum' sırasında (gerçekten nasıl demeniz gerektiğini bilmiyorum) 'GetHashCode' sabit bir değer döndürmelidir. Uygulamanızı açtığınızı, DB'den (varlık) bir nesnenin bir örneğini yüklediğinizi ve bunun karma kodunu aldığınızı varsayalım. Belli bir sayı döndürür. Uygulamayı kapatın ve aynı varlığı yükleyin. Bu sefer hashcode'un varlığı ilk kez yüklediğiniz değerle aynı olması gerekir mi? IMHO, değil.


1
Örneğinize göre Jeff Yates, karma kodu değiştirilebilir verilere dayandıramayacağınızı söylüyor. Sözlükteki değiştirilebilir bir nesneyi yapıştıramaz ve karma kodu bu nesnenin değişken değerlerine dayanıyorsa, nesnenin iyi çalışmasını bekleyemezsiniz.
Ogre Psalm33

3
MSDN kuralının nerede ihlal edildiğini göremiyorum? Kural açıkça şunu söylüyor: Bir nesnenin GetHashCode yöntemi , nesnenin Eşittir yönteminin dönüş değerini belirleyen nesne durumunda herhangi bir değişiklik olmadığı sürece tutarlı olarak aynı karma kodunu döndürmelidir . Bu,
example2'nin adını

8

Bu iyi bir tavsiye. Brian Pepin'in konu hakkında söyledikleri:

Bu beni bir kereden fazla tetikledi: GetHashCode'un bir örneğin ömrü boyunca her zaman aynı değeri döndürdüğünden emin olun. Çoğu hashtable uygulamasında "kodları" tanımlamak için hash kodlarının kullanıldığını unutmayın. Bir nesnenin "kovası" değişirse, bir karma tablo nesnenizi bulamayabilir. Bunlar bulmak çok zor hatalar olabilir, bu yüzden ilk seferde doğru olsun.


Ben aşağı oy vermedim, ama sanırım başkaları yaptı çünkü tüm sorunu kapsayan bir teklif. Rol dizeleri değiştirilebilirdi, ancak karma kodlarını değiştirmedi. "Bob" oluşturur, hashtable içinde bir anahtar olarak kullanır ve değerini "phil" olarak değiştirirsiniz. Sonra yeni bir dize "phil" oluşturun. daha sonra "phil" anahtarıyla bir karma tablo girişi ararsanız, başlangıçta koyduğunuz öğe bulunmaz. Birisi "bob" için arama yaptıysa, bu olurdu, ancak artık doğru olmayabilecek bir değer elde edersiniz. Değişken olan anahtarları kullanmamaya özen gösterin ya da tehlikelerin farkında olun.
Eric Tuttleman

@EricTuttleman: Bir çerçeve için kurallar yazsaydım, herhangi bir nesne çifti için belirtirdim Xve bir Ykez X.Equals(Y)veya Y.Equals(X)çağrıldığında, gelecekteki tüm çağrılar aynı sonucu vermelidir. Eğer biri başka bir eşitlik tanımı kullanmak istiyorsa, an EqualityComparer<T>.
SuperCat

5

Sorunuzu doğrudan yanıtlamıyor, ancak - Resharper kullanıyorsanız, sizin için makul bir GetHashCode uygulaması (ve Eşittir yöntemi) üreten bir özelliğe sahip olduğunu unutmayın. Karma kodu hesaplarken elbette sınıfın hangi üyelerinin dikkate alınacağını belirleyebilirsiniz.


Teşekkürler, aslında hiç Resharper kullanmadım ama sık sık bahsettiğini görmeye devam ediyorum, bu yüzden denemeliyim.
Joan Venge

+1 Resharper, varsa güzel bir GetHashCode uygulaması oluşturur.
ΩmegaMan

5

Marc Brooks'un bu blog gönderisine göz atın:

VTO'lar, RTO'lar ve GetHashCode () - oh, benim!

Ve daha sonra tartışan ve ilk uygulamadaki bazı küçük zayıflıkları kapsayan takip postasına bakın (yeni olduğum gibi bağlantı kuramıyorum, ancak initlal makalesinde bir bağlantı var).

Bu, GetHashCode () uygulaması oluşturma hakkında bilmem gereken her şeydi, hatta yönteminin bir kısmını diğer bazı yardımcı programlarla birlikte kısa altın olarak indiriyor.


4

Hashcode asla değişmez, ancak Hashcode'un nereden geldiğini anlamak da önemlidir.

Nesneniz değer anlambilimi kullanıyorsa, yani nesnenin kimliği değerleriyle tanımlanır (Dize, Renk, tüm yapılar gibi). Nesnenizin kimliği tüm değerlerinden bağımsızsa, Hashcode değerlerinin bir alt kümesiyle tanımlanır. Örneğin, StackOverflow girdiniz bir yerde bir veritabanında saklanır. Adınızı veya e-postanızı değiştirirseniz, bazı değerler değişse de müşteri girişiniz aynı kalır (sonuçta genellikle uzun bir müşteri kimliği # ile tanımlanırsınız).

Kısacası:

Değer türü semantiği - Hashcode değerler tarafından tanımlanır. Referans türü semantik - Hashcode bazı kimliklerle tanımlanır

Eric Evans tarafından Domain Driven Design'ı okumanızı öneririm, eğer bu hala mantıklı değilse, değer türlerine (yukarıda yapmaya çalıştığım az çok şey) karşı varlıklara girer.


Bu gerçekten doğru değil. Karma kodu belirli bir örnek için sabit kalmalıdır. Değer türleri söz konusu olduğunda, genellikle her bir değerin benzersiz bir örnek olduğu ve bu nedenle karma değiştiğini, ancak aslında yeni bir örneğinin olduğu durumdur.
Jeff Yates

Haklısınız, değer türleri değişmez, bu yüzden değişmeyi engellerler. İyi yakalama.
DavidN

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.