Jak działa domyślna implementacja GetHashCode()
? I czy wystarczająco dobrze radzi sobie ze strukturami, klasami, tablicami itp.?
Próbuję zdecydować, w jakich przypadkach powinienem spakować własne iw jakich przypadkach mogę bezpiecznie polegać na domyślnej implementacji, aby dobrze się spisać. Nie chcę wymyślać koła na nowo, jeśli to w ogóle możliwe.
.net
hash
gethashcode
Fung
źródło
źródło
GetHashCode()
został zastąpiony), używającSystem.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj)
Odpowiedzi:
InternalGetHashCode jest mapowane na funkcję ObjectNative :: GetHashCode w środowisku CLR, która wygląda następująco:
Pełna implementacja GetHashCodeEx jest dość duża, więc łatwiej jest po prostu utworzyć link do kodu źródłowego C ++ .
źródło
string
zastępujeGetHashCode
. Z drugiej strony załóżmy, że chcesz zliczać, ile razy różne kontrolki przetwarzająPaint
zdarzenia. Możesz użyćDictionary<Object, int[]>
(każdyint[]
przechowywany może pomieścić dokładnie jeden przedmiot).W przypadku klasy wartości domyślne to zasadniczo równość odwołań i zwykle jest to w porządku. Pisząc strukturę, częściej zastępuje się równość (nie tylko w celu uniknięcia boksu), ale i tak bardzo rzadko piszesz strukturę!
Przesłaniając równość, zawsze powinieneś mieć dopasowanie
Equals()
iGetHashCode()
(tj. Dla dwóch wartości, jeśliEquals()
zwraca prawdę, muszą zwrócić ten sam kod skrótu, ale odwrotność nie jest wymagana) - i często podaje się również operatory==
/!=
, a często wdrożyćIEquatable<T>
też.Do generowania kodu skrótu często używa się sumy faktoryzowanej, ponieważ pozwala to uniknąć kolizji sparowanych wartości - na przykład dla podstawowego skrótu 2 pól:
Ma to tę zaletę, że:
etc - co może być powszechne, jeśli używa się tylko nieważonej sumy lub xor (
^
) itp.źródło
unchecked
. Na szczęścieunchecked
jest to opcja domyślna w C #, ale lepiej byłoby to wyraźnie określić; edytowanyDokumentacja
GetHashCode
metody dla Object mówi, że „domyślna implementacja tej metody nie może być używana jako unikalny identyfikator obiektu do celów mieszania”. a ten dla ValueType mówi „Jeśli wywołasz metodę GetHashCode typu pochodnego, wartość zwracana prawdopodobnie nie będzie odpowiednia do użycia jako klucz w tabeli skrótów”. .Podstawowe typy danych, takich jak
byte
,short
,int
,long
,char
istring
wdrożyć metodę dobry GetHashCode. Niektóre inne klasy i struktury, jakPoint
na przykład, implementująGetHashCode
metodę, która może, ale nie musi być odpowiednia dla twoich konkretnych potrzeb. Po prostu musisz go wypróbować, aby zobaczyć, czy jest wystarczająco dobry.Dokumentacja dla każdej klasy lub struktury może powiedzieć, czy zastępuje domyślną implementację, czy nie. Jeśli to nie zastępuje, powinieneś użyć własnej implementacji. Dla wszystkich klas lub struktur, które tworzysz samodzielnie, w których musisz użyć
GetHashCode
metody, powinieneś utworzyć własną implementację, która używa odpowiednich członków do obliczenia kodu skrótu.źródło
Ponieważ nie mogłem znaleźć odpowiedzi, która wyjaśnia, dlaczego powinniśmy nadpisywać
GetHashCode
iEquals
dla struktur niestandardowych oraz dlaczego domyślna implementacja „prawdopodobnie nie będzie odpowiednia do użycia jako klucz w tablicy skrótów”, zostawię link do tego bloga post , który wyjaśnia, dlaczego na przykładzie rzeczywistego problemu, który się wydarzył.Polecam przeczytanie całego posta, ale tutaj jest podsumowanie (podkreślenie i dodane wyjaśnienia).
Powód, dla którego domyślny skrót dla struktur jest powolny i niezbyt dobry:
Rzeczywisty problem opisany w poście:
Tak więc, aby odpowiedzieć na pytanie „w jakich przypadkach powinienem spakować swoją własną iw jakich przypadkach mogę bezpiecznie polegać na domyślnej implementacji”, przynajmniej w przypadku struktur , należy nadpisać
Equals
iGetHashCode
zawsze, gdy niestandardowa struktura może być używana jako klucz w tablicy skrótów lubDictionary
.Poleciłbym również wdrożenie
IEquatable<T>
w tym przypadku, aby uniknąć boksu.Jak powiedziały inne odpowiedzi, jeśli piszesz klasę , domyślny skrót używający równości odwołań jest zwykle w porządku, więc nie zawracałbym sobie w tym przypadku, chyba że musisz nadpisać
Equals
(wtedy musiałbyś odpowiednio nadpisaćGetHashCode
).źródło
Ogólnie rzecz biorąc, jeśli zastępujesz Equals, chcesz zastąpić GetHashCode. Powodem tego jest to, że oba są używane do porównywania równości twojej klasy / struktury.
Równe jest używane podczas sprawdzania Foo A, B;
jeśli (A == B)
Ponieważ wiemy, że wskaźnik prawdopodobnie nie będzie pasował, możemy porównać elementy wewnętrzne.
GetHashCode jest zwykle używany przez tablice skrótów. Kod skrótu wygenerowany przez twoją klasę powinien zawsze być taki sam dla klas podanych w stanie.
Zazwyczaj tak
Niektórzy powiedzą, że hashcode powinien być obliczany tylko raz na okres istnienia obiektu, ale ja się z tym nie zgadzam (i prawdopodobnie się mylę).
Używając domyślnej implementacji dostarczonej przez obiekt, o ile nie masz tego samego odwołania do jednej z twoich klas, nie będą one sobie równe. Zastępując Equals i GetHashCode, możesz zgłosić równość na podstawie wartości wewnętrznych, a nie odwołań do obiektów.
źródło
Jeśli masz do czynienia tylko z POCO, możesz użyć tego narzędzia, aby nieco uprościć swoje życie:
...
źródło