Ben açıklıyor bir cevap bulamadık yana neden biz geçersiz kılmak gerekir GetHashCode
ve Equals
özel yapılar için ve neden varsayılan uygulamadır "değil muhtemel bir karma tablodaki bir anahtar olarak kullanıma uygun olması için" ben bir bağlantı bırakacağım bu blog Bu sorunun nedenini gerçek bir örnekle açıklayan yazı .
Tüm yazıyı okumanızı tavsiye ederim, ama işte bir özet (vurgu ve açıklamalar eklendi).
Yapılar için varsayılan karmanın yavaş olması ve çok iyi olmaması nedeni:
CLR'nin tasarlanma şekli, tanımlanmış bir üyeye System.ValueType
veya System.Enum
tiplere [[]] bir boks tahsisine neden olabilir [...]
Bir karma işlevinin uygulayıcısı bir ikilemle karşı karşıyadır: karma işlevinin iyi bir dağılımını yapın veya hızlı hale getirin. Bazı durumlarda, bu ikisini de elde etmek mümkündür, ama öyle jenerik bunu yapmak zor içinde ValueType.GetHashCode
.
Bir yapının standart karma işlevi, tüm alanların karma kodlarını "birleştirir". Ancak bir ValueType
yöntemde bir alanın karma kodunu almanın tek yolu yansıma kullanmaktır . Bu nedenle, CLR yazarları dağıtım hızı üzerinden ticaret yapmaya karar verdiler ve varsayılan GetHashCode
sürüm sadece bir ilk boş olmayan alanın karma kodunu döndürür ve bunu bir tür id ile "munges" döndürür [...] . Örneğin, yeterince şanssızsanız ve yapınızın ilk alanı çoğu örnek için aynı değere sahipse, bir karma işlevi her zaman aynı sonucu sağlayacaktır . Tahmin edebileceğiniz gibi, bu örnekler bir karma kümesinde veya bir karma tablosunda saklanırsa, bu ciddi bir performans etkisine neden olacaktır.
[...] Yansıtma tabanlı uygulama yavaş . Çok yavaş.
[...] Her iki ValueType.Equals
ve ValueType.GetHashCode
özel optimizasyon var. Bir tür "işaretçiler" içermiyorsa ve uygun şekilde paketlenmişse [...] daha uygun sürümler kullanılır: GetHashCode
bir örnek üzerinden yinelenir ve 4 baytlık XORs blokları ve Equals
yöntem kullanılarak iki örneği karşılaştırır memcmp
. [...] Ama optimizasyon çok zor. Birincisi, optimizasyonun ne zaman etkinleştirildiğini bilmek zordur [...] İkincisi, bir bellek karşılaştırması size doğru sonuçları vermez . İşte basit bir örnek: [...] -0.0
ve +0.0
eşittir ancak farklı ikili gösterimlere sahiptirler.
Postada açıklanan gerçek dünya sorunu:
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
Varsayılan eşitlik uygulamasına sahip özel bir yapı içeren bir demet kullandık. Ne yazık ki, yapı neredeyse her zaman [boş dize] 'ye eşit olan isteğe bağlı bir ilk alana sahipti . Setteki öğelerin sayısı önemli ölçüde artıncaya kadar performans iyiydi, bu da on binlerce öğeden oluşan bir koleksiyonu başlatmak için dakikalar alarak gerçek bir performans sorununa neden oldu.
Bu nedenle, "hangi durumlarda kendimi paketlemeliyim ve hangi durumlarda varsayılan uygulamaya güvenebilirim" sorusunu yanıtlamak için, en azından yapılar söz konusu olduğunda, geçersiz kılmalısınızEquals
ve GetHashCode
ne zaman özel yapınız bir hash tablosundaki anahtar veya Dictionary
.
Ayrıca IEquatable<T>
boksu önlemek için bu durumda uygulamayı tavsiye ederim .
Diğer cevapların söylediği gibi, bir sınıf yazıyorsanız , referans eşitliğini kullanan varsayılan karma genellikle iyidir, bu yüzden geçersiz kılmanıza gerek kalmadıkça bu durumda rahatsız etmem Equals
(o zaman buna GetHashCode
göre geçersiz kılmanız gerekir).