Null değerinin karma kodu her zaman sıfır olmalıdır, .NET'te


88

Bir set üyesi olarak System.Collections.Generic.HashSet<>kabul etmek nullgibi koleksiyonlar göz önüne alındığında , hash kodunun ne nullolması gerektiği sorulabilir. Çerçeve 0şunları kullanıyor gibi görünüyor :

// nullable struct type
int? i = null;
i.GetHashCode();  // gives 0
EqualityComparer<int?>.Default.GetHashCode(i);  // gives 0

// class type
CultureInfo c = null;
EqualityComparer<CultureInfo>.Default.GetHashCode(c);  // gives 0

Bu, null yapılabilir numaralandırmalarla (biraz) sorunlu olabilir. Eğer tanımlarsak

enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

daha sonra Nullable<Season>(aynı zamanda denir Season?) yalnızca beş değer alabilir, ancak bunlardan ikisi, yani nullve Season.Springaynı hash koduna sahiptir.

Bunun gibi "daha iyi" bir eşitlik karşılaştırıcısı yazmak cazip geliyor:

class NewNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? Default.GetHashCode(x) : -1;
  }
}

Ama hash kodunun nullolması için herhangi bir sebep var 0mı?

DÜZENLEME / EKLEME:

Bazı insanlar bunun ağır basmakla ilgili olduğunu düşünüyor Object.GetHashCode(). Aslında değil. (.NET yazarları geçersiz kılmasını yaptınız mı GetHashCode()içinde Nullable<>yapı olduğu olsa alakalı.) Parametresiz bir kullanıcı yazılı uygulanmasını GetHashCode()kimin hash kodu aradığımız nesne olduğu durumu idare asla null.

Bu, soyut yöntemin EqualityComparer<T>.GetHashCode(T)uygulanması veya başka bir şekilde arayüz yönteminin uygulanmasıyla ilgilidir IEqualityComparer<T>.GetHashCode(T). Şimdi, MSDN'ye bu bağlantıları oluştururken, orada bu yöntemlerin ArgumentNullExceptionyegane argümanları ise bir attığını söylediğini görüyorum null. Bu kesinlikle MSDN'de bir hata olmalı? .NET'in kendi uygulamalarından hiçbiri özel durum oluşturmaz. Bu durumda fırlatma, nullbir HashSet<>. Sürece HashSet<>bir uğraşırken olağanüstü bir şey yapar nullöğe (Testten o gerekecektir).

YENİ DÜZENLEME / EKLEME:

Şimdi hata ayıklamayı denedim. İle HashSet<>, ben varsayılan eşitlik karşılaştırıcısı ile değerlerini teyit edebiliriz Season.Springve null olacak aynı kovada sonunda. Bu, özel dizi üyelerini çok dikkatli bir şekilde inceleyerek belirlenebilir m_bucketsve m_slots. Endekslerin her zaman tasarım gereği birer birer kaydırıldığına dikkat edin.

Ancak yukarıda verdiğim kod bunu düzeltmiyor. Görünüşe göre HashSet<>, değerin ne zaman olduğu eşitlik karşılaştırıcısına asla sormayacak null. Bu, kaynak kodundan alınmıştır HashSet<>:

    // Workaround Comparers that throw ArgumentNullException for GetHashCode(null).
    private int InternalGetHashCode(T item) {
        if (item == null) { 
            return 0;
        } 
        return m_comparer.GetHashCode(item) & Lower31BitMask; 
    }

Bu, en azından için HashSet<>hash değerini değiştirmenin bile mümkün olmadığınull anlamına gelir . Bunun yerine, bir çözüm, diğer tüm değerlerin karmasını şu şekilde değiştirmektir:

class NewerNullEnumEqComp<T> : EqualityComparer<T?> where T : struct
{
  public override bool Equals(T? x, T? y)
  {
    return Default.Equals(x, y);
  }
  public override int GetHashCode(T? x)
  {
    return x.HasValue ? 1 + Default.GetHashCode(x) : /* not seen by HashSet: */ 0;
  }
}

26
Null için hash kodu neden sıfır olmamalı? Bir hash çarpışması dünyanın sonu değil, biliyorsun.
Hot Licks

3
İyi bilinen, oldukça yaygın bir çarpışma olması dışında. Değil temiz olmadığını kötü ve hatta bir sorun önemli olduğunu, sadece kolayca önlenebilir bulunuyor
Chris Pfohl

8
lol neden ".NET çerçevesi bir köprüden
atlarsa

3
Merak ettiğim için, boş bir sezon nasıl olurdu?
SwDevMan81

1
Kişisel olarak, bu yüzden numaralarıma her zaman ilk değer olarak "Boş" veya "Bilinmeyen" değerini veriyorum. Bu şekilde, benim açık bir şekilde değeri belirlemeden ve dolayısıyla sorunu geçersiz kılan Sezon numaram hiçbir zaman Baharı temsil etmeyecektir.
Chris

Yanıtlar:


25

Boş değerler için döndürülen karma kod tür için tutarlı olduğu sürece sorun yaşamazsınız. Bir karma kod için tek gereksinim, eşit kabul edilen iki nesnenin aynı karma kodu paylaşmasıdır.

Boş değer için 0 veya -1 döndürmek, birini seçip her zaman döndürdüğünüz sürece işe yarayacaktır. Açıkçası, boş olmayan karma kodlar, boş için kullandığınız değeri döndürmemelidir.

Benzer sorular:

Boş alanlarda GetHashCode?

Nesnenin tanımlayıcısı boş olduğunda GetHashCode ne döndürmelidir?

Bu MSDN girişinin "Açıklamaları" , karma kod çevresinde daha ayrıntılı olarak ele alınır. Dokunaklı, dokümantasyon herhangi kapsama veya boş değerler tartışmaya sağlamaz hiç bile topluluk içeriğinde -.

Numaralandırmayla ilgili sorununuzu çözmek için, sıfır olmayan döndürmek için karma kodunu yeniden uygulayın, null değerine eşdeğer bir varsayılan "bilinmeyen" enum girişi ekleyin veya boş verilebilir numaralandırmaları kullanmayın.

Bu arada ilginç buldum.

Bununla ilgili genel olarak gördüğüm başka bir sorun da, karma kodun en az bir çarpışma olmadan null yapılabilir 4 bayt veya daha büyük bir türü temsil edememesidir (tür boyutu arttıkça daha fazla). Örneğin, bir int'in karma kodu yalnızca int olduğundan, tam int aralığını kullanır. Null için bu aralıktaki hangi değeri seçersiniz? Hangisini seçerseniz seçin, değerin hash kodunun kendisi ile çarpışacaktır.

Çarpışmalar kendi içlerinde bir sorun olmak zorunda değildir, ancak orada olduklarını bilmeniz gerekir. Karma kodlar yalnızca bazı durumlarda kullanılır. MSDN'deki belgelerde belirtildiği gibi, karma kodların farklı nesneler için farklı değerler döndürmesi garanti edilmediğinden beklenmemelidir.


Bağladığınız soruların tamamen benzer olduğunu düşünmüyorum. Object.GetHashCode()Kendi sınıfınızda (veya yapınızda) geçersiz kıldığınızda, bu kodun yalnızca insanlar gerçekten sınıfınızın bir örneğine sahip olduklarında vurulacağını bilirsiniz . Bu örnek olamaz null. Bunun size geçersiz kılma başlamayın yüzden Object.GetHashCode()birlikte if (this == null) return -1;"olmak arasında fark var null" dır bazı alanları sahip bir nesne olma "ve null".
Jeppe Stig Nielsen

Diyorsunuz ki: Açıkçası, boş olmayan karma kodlar, null için kullandığınız değeri döndürmemelidir. Bu ideal olur, katılıyorum. Ve sorumu ilk başta sormamın nedeni budur, çünkü ne zaman bir enum yazdığımızda T, o zaman (T?)nullve (T?)default(T)aynı karma koda sahip olacağız (mevcut .NET uygulamasında). .NET uygulamacılarıdır ya hash kodu değiştirdiyseniz Yani değiştirilebilir null veya karma kodu algoritma System.Enum.
Jeppe Stig Nielsen

Bağlantıların boş dahili alanlar için olduğunu kabul ediyorum. IEqualityComparer <T> için olduğunu söylüyorsunuz, uygulamanızda karma kod hala bir türe özeldir, bu nedenle hala aynı durumda, tür için tutarlılıktasınız. Boş değerlerin bir türü olmadığından, herhangi bir türdeki boş değerler için aynı hash kodunu döndürmek önemli olmayacaktır.
Adam Houldsworth

1
Not: Sorumu iki kez güncelledim. (En azından ile HashSet<>) hash kodunu değiştirmenin işe yaramadığı ortaya çıktı null.
Jeppe Stig Nielsen

6

Karma kodun yalnızca eşitliği belirlemede ilk adım olarak kullanıldığını ve iki nesnenin eşit olup olmadığına ilişkin fiili bir belirleme olarak asla kullanılmayacağını (kullanılmaması gerektiğini) unutmayın.

Eğer iki nesnenin hash kodları eşit değilse, o zaman eşit değil olarak kabul edilirler (çünkü ilişkisiz uygulamanın doğru olduğunu varsayıyoruz - yani bunu ikinci kez tahmin etmiyoruz). Aynı hash koduna sahiplerse, o zaman gerçek eşitlik açısından kontrol edilmelidir , sizin durumunuzda nullve enum değeri başarısız olacaktır.

Sonuç olarak - genel durumda sıfır kullanmak diğer tüm değerler kadar iyidir.

Elbette, numaranız gibi, bu sıfırın gerçek bir değerin karma koduyla paylaşıldığı durumlar olacaktır . Soru, sizin için ek bir karşılaştırmanın çok küçük ek yükünün sorunlara yol açıp açmayacağıdır.

Eğer öyleyse, o belirli türü için Nullable durumunda için kendi karşılaştırıcısı tanımlamak ve boş değeri her zaman (tabii!) Her zaman aynı olan bir karma kodu vermesini sağlamak ve altta yatan tarafından elde edilemeyen bir değer türün kendi hash kodu algoritması. Kendi tipleriniz için bu yapılabilir. Diğerleri için - iyi şanslar :)


5

Sıfır olmak zorunda değil - istersen 42 yapabilirsin.

Önemli olan tek şey programın yürütülmesi sırasında tutarlılıktır .

Bu sadece en açık temsildir, çünkü nullgenellikle dahili olarak sıfır olarak gösterilir. Bunun anlamı, hata ayıklama sırasında sıfır karma kodu görürseniz, "Hmm .. bu bir boş referans sorunu muydu?" Diye düşünmenizi isteyebilir.

Gibi bir sayı kullanırsanız 0xDEADBEEF, o zaman birisi sizin sihirli bir sayı kullandığınızı söyleyebilir ... ve siz de öyle olursunuz. (Sıfırın da sihirli bir sayı olduğunu söyleyebilirsiniz ve haklısınızdır ... çok yaygın olarak kullanılması ve kuralın bir şekilde istisnası olması dışında.)


4

İyi soru.

Bunu kodlamaya çalıştım:

enum Season
{
  Spring,
  Summer,
  Autumn,
  Winter,
}

ve bunu şu şekilde yürütün:

Season? v = null;
Console.WriteLine(v);

geri döner null

yaparsam normal yerine

Season? v = Season.Spring;
Console.WriteLine((int)v);

o dönmek 0beklendiği gibi, veya basit Bahar biz döküm kaçının eğerint .

Yani .. aşağıdakileri yaparsanız:

Season? v = Season.Spring;  
Season? vnull = null;   
if(vnull == v) // never TRUE

DÜZENLE

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

Başka bir deyişle: iki nesnenin aynı karma kodu varsa, bu onların eşit oldukları anlamına gelmez, çünkü gerçek eşitlik Eşittir .

Tekrar MSDN'den:

Bir nesnenin GetHashCode yöntemi, nesnenin Equals 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ütmesi 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.


6
Çarpışma, tanımı gereği, iki eşit olmayan nesnenin aynı hashcode'a sahip olduğu anlamına gelir. Nesnelerin eşit olmadığını kanıtladınız. Şimdi aynı hash koduna sahipler mi? OP'ye göre yaptıkları, bunun bir çarpışma olduğu anlamına geliyor. Şimdi, bir çarpışmanın olması dünyanın sonu değil, bu sadece 0'dan başka bir şeye sıfır hash uygulanmasından daha muhtemel bir çarpışma, bu da performansı düşürüyor.
Servy

1
Peki cevabınız gerçekte ne diyor? Season.Spring'in null'a eşit olmadığını söylüyorsunuz. Eh, bu yanlış değil, ama soruya şu anda hiçbir şekilde cevap vermiyor.
Servy

2
@Servy: soru şunu söylüyor: Neden aynı 2 farklı nesne için aynı hascode'a sahibim ( null ve Spring ). Yani cevap, aynı hashcode'a sahip olsalar bile, çarpışma nedeni olmadığıdır, bu arada, bunlar eşit değildir.
Tigran

3
"Cevap: neden olmasın?" OP, "neden olmasın" sorunuzu önceden cevapladı. Başka bir numaraya göre çarpışmaya neden olma olasılığı daha yüksektir. 0'ın seçilmesinin bir nedeni olup olmadığını merak ediyordu ve şimdiye kadar kimse cevap vermedi.
Servy

1
Bu cevap, OP'nin zaten bilmediği hiçbir şeyi içermiyor, sorunun sorulma şeklinden açıkça anlaşılıyor.
Konrad Rudolph

4

Ancak null'un karma kodunun 0 olması için herhangi bir sebep var mı?

Herhangi bir şey olabilirdi. 0'ın mutlaka en iyi seçim olmadığı konusunda hemfikirim, ancak muhtemelen en az hataya yol açan bir seçenektir.

Bir hash fonksiyonu kesinlikle olmalı aynı değerde aynı hash değerini döndürür. Bunu yapan bir bileşen var olduğunda , hash değeri için gerçekten tek geçerli değer budur null. Bunun için hm gibi bir sabit olsaydı, object.HashOfNullan uygulayan birinin IEqualityComparerbu değeri kullanması gerektiğini bilmesi gerekirdi. Bunu düşünmezlerse, 0 kullanma şansları diğer tüm değerlerden biraz daha yüksektir, sanırım.

en azından HashSet <> için, null değerinin karmasını değiştirmek bile mümkün değildir

Yukarıda belirtildiği gibi, tamamen imkansız olduğunu düşünüyorum, çünkü zaten konvansiyonu izleyen null hash değeri 0 olan türler var.


Kişi izin veren EqualityComparer<T>.GetHashCode(T)belirli bir tür için yöntemi uyguladığında , argüman olduğu zaman bir şey yapmak zorundadır . (1) bir , (2) geri dönüş veya (3) başka bir şey iade edebilirsiniz. Cevabınızı her zaman böyle bir durumda geri dönecek bir öneri için alıyorum ? TnullnullArgumentNullException00
Jeppe Stig Nielsen

@JeppeStigNielsen Fırlat - geri dönüş konusunda emin değilim, ancak geri dönmeyi seçerseniz, kesinlikle sıfır.
Roman Starkov

2

Basitlik uğruna 0'dır. Böyle bir zorunluluk yok. Yalnızca hash kodlamanın genel gereksinimlerini sağlamanız gerekir.

Örneğin, iki nesne eşitse, hashcode'larının da her zaman eşit olması gerektiğinden emin olmanız gerekir . Bu nedenle, farklı karma kodlar her zaman farklı nesneleri temsil etmelidir (ancak bunun tersi de doğru değildir: iki farklı nesne aynı karma koduna sahip olabilir, bu sık sık gerçekleşse bile, bu iyi kalitede bir karma işlevi değildir - bir iyi çarpışma direnci).

Tabii ki, cevabımı matematiksel nitelikteki gerekliliklerle sınırladım. .NET özgü, okuyabilir sıra teknik şartlar vardır burada . Boş değer için 0 bunların arasında değildir.


1

Yani bu bir Unknownenum değeri kullanılarak önlenebilir (a'nın Seasonbilinmemesi biraz garip görünse de). Yani böyle bir şey bu sorunu çürütür:

public enum Season
{
   Unknown = 0,
   Spring,
   Summer,
   Autumn,
   Winter
}

Season some_season = Season.Unknown;
int code = some_season.GetHashCode(); // 0
some_season = Season.Autumn;
code = some_season.GetHashCode(); // 3

O zaman her sezon için benzersiz hash kod değerlerine sahip olursunuz.


1
evet, ama bu aslında soruyu anlamıyor. Bu şekilde null soruya göre Uknown ile çarpışacak. Fark nedir?
Tigran

@Tigran - Bu sürüm null yapılabilir bir tür
kullanmıyor

Anlıyorum, ama soru null yapılabilir türle ilgili.
Tigran

SO'da insanların yanıt olarak iyileştirme önerileri sunduğu milyonlarca kez sahnem var.
SwDevMan81

1

Şahsen ben null yapılabilir değerleri kullanmayı biraz garip buluyorum ve mümkün olduğunca onlardan kaçınmaya çalışıyorum. Sorunun sadece başka bir sebep. Bazen çok kullanışlıdırlar, ancak benim genel kuralım, değer türlerini mümkünse sıfırla karıştırmak değil, çünkü bunlar iki farklı dünyadan. .NET çerçevesinde de aynı şeyi yapıyor gibi görünüyorlar - birçok değer türü TryParse, değerleri değer olmayanlardan ( null) ayırmanın bir yolu olan yöntemi sağlar .

Özel durumunuzda, problemden kurtulmak kolaydır çünkü kendi tipinizle başa çıkarsınız Season.

(Season?)nullbana göre, bazı alanların gerekli olmadığı bir web formunuz olduğunda olduğu gibi 'sezon belirtilmedi' anlamına gelir. Bana göre enumbiraz hantal kullanmaktansa kendi içinde bu özel 'değeri' belirtmek daha iyidir Nullable<T>. Daha hızlı (boks yok) daha kolay okunacak ( Season.NotSpecifiedvsnull ) olacak ve sorununuzu karma kodlarla çözecektir.

Elbette, intdeğer alanını genişletemeyeceğiniz ve değerlerden birini özel olarak adlandırmanız gibi diğer türler için her zaman mümkün değildir. Fakatint? karma kod çarpışması çok daha küçük bir sorundur.


"Boks" dediğinizde, sanırım "sarmalamak" demek istiyorsunuz, yani bir yapının içine bir yapı değeri koymak Nullable<>( HasValueüye daha sonra buraya ayarlanacaktır true). Sorunun gerçekten daha küçük olduğundan emin misin int?? Çoğu zaman bir kişi sadece birkaç değerini kullanır intve daha sonra bir enum'a eşdeğerdir (teoride birçok üyesi olabilir).
Jeppe Stig Nielsen

Genel olarak, gerekli bilinen değerlerin sınırlı sayıda olması durumunda enum'un seçildiğini söyleyebilirim (2-10). Limit daha büyükse veya hiç değilse int, daha mantıklı. Elbette tercihler değişir.
Maciej

0
Tuple.Create( (object) null! ).GetHashCode() // 0
Tuple.Create( 0 ).GetHashCode() // 0
Tuple.Create( 1 ).GetHashCode() // 1
Tuple.Create( 2 ).GetHashCode() // 2

1
Bu ilginç bir yaklaşım. Cevabınızı bazı ek açıklamalar içerecek şekilde ve özellikle sorunun doğası dikkate alındığında düzenlemeniz faydalı olacaktır.
Jeremy Caney
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.