Biorąc pod uwagę następującą klasę
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
Przesłoniłem Equals
metodę, ponieważ Foo
reprezentują wiersz dla Foo
tabeli s. Jaka jest preferowana metoda zastępowania GetHashCode
?
Dlaczego warto to zmienić GetHashCode
?
c#
overriding
hashcode
David Basarab
źródło
źródło
Odpowiedzi:
Tak, ważne jest, aby twój przedmiot był używany jako klucz w słowniku,
HashSet<T>
itp. - ponieważ jest on używany (w przypadku braku niestandardowegoIEqualityComparer<T>
) do grupowania przedmiotów w wiadra. Jeśli kod skrótu dla dwóch elementów nie pasuje, nigdy nie można ich uznać za równe ( Equals po prostu nigdy nie zostanie wywołany).Metoda GetHashCode () powinna odzwierciedlać
Equals
logikę; zasady są następujące:Equals(...) == true
), muszą zwrócić tę samą wartość dlaGetHashCode()
GetHashCode()
jest równy, nie musi być taki sam; jest to kolizja iEquals
zostanie wezwana, aby sprawdzić, czy jest to prawdziwa równość, czy nie.W tym przypadku wygląda na to, że „
return FooId;
” jest odpowiedniąGetHashCode()
implementacją. Jeśli testujesz wiele właściwości, często łączy się je za pomocą kodu jak poniżej, aby zmniejszyć kolizje po przekątnej (tj. Tak, żenew Foo(3,5)
ma inny kod skrótu niżnew Foo(5,3)
):Och - dla wygody możesz również rozważyć dostarczenie
==
i!=
operatorów podczas nadpisywaniaEquals
iGetHashCode
.Wykazanie tego, co się dzieje, kiedy się to źle jest tutaj .
źródło
W rzeczywistości bardzo trudno jest go
GetHashCode()
poprawnie wdrożyć, ponieważ oprócz reguł, o których już wspomniał Marc, kod skrótu nie powinien się zmieniać w trakcie życia obiektu. Dlatego pola używane do obliczania kodu skrótu muszą być niezmienne.W końcu znalazłem rozwiązanie tego problemu, kiedy pracowałem z NHibernate. Moje podejście polega na obliczeniu kodu skrótu na podstawie identyfikatora obiektu. Identyfikator można ustawić tylko za pomocą konstruktora, więc jeśli chcesz zmienić identyfikator, co jest bardzo mało prawdopodobne, musisz utworzyć nowy obiekt, który ma nowy identyfikator, a zatem nowy kod skrótu. To podejście najlepiej działa z identyfikatorami GUID, ponieważ można dostarczyć konstruktora bez parametrów, który losowo generuje identyfikator.
źródło
Przesłaniając Equals, po prostu stwierdzasz, że jesteś tym, który wie lepiej, jak porównywać dwa wystąpienia danego typu, więc prawdopodobnie będziesz najlepszym kandydatem do zapewnienia najlepszego kodu skrótu.
Oto przykład, jak ReSharper pisze dla ciebie funkcję GetHashCode ():
Jak widać, po prostu próbuje odgadnąć dobry kod skrótu na podstawie wszystkich pól w klasie, ale skoro znasz domenę lub zakresy wartości swojego obiektu, nadal możesz podać lepszy.
źródło
0 ^ a = a
, więc0 ^ m_someVar1 = m_someVar1
. Równie dobrze mógłby ustawić wartość początkowąresult
nam_someVar1
.Nie zapomnij sprawdzić parametru obj przed
null
zastąpieniemEquals()
. A także porównaj typ.Powodem tego jest:
Equals
musi zwrócić false w porównaniu donull
. Zobacz także http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxźródło
obj
rzeczywiście jest równa,this
bez względu na to, jak wywołano Equals () z klasy podstawowej.fooItem
do góry, a następnie sprawdzenie jej pod kątem wartości NULL będzie działać lepiej w przypadku wartości NULL lub niewłaściwego typu.obj as Foo
byłoby nieprawidłowe.Co powiesz na:
źródło
string.Format
. Innym naukowym, którego widziałem, jestnew { prop1, prop2, prop3 }.GetHashCode()
. Nie mogę komentować, który z nich byłby wolniejszy. Nie nadużywaj narzędzi.{ prop1="_X", prop2="Y", prop3="Z" }
i{ prop1="", prop2="X_Y", prop3="Z_" }
. Prawdopodobnie tego nie chcesz.Mamy dwa problemy do rozwiązania.
Nie można podać sensownego,
GetHashCode()
jeśli można zmienić dowolne pole w obiekcie. Często też obiekt NIGDY nie będzie używany w kolekcji, od której zależyGetHashCode()
. Tak więc koszt wdrożeniaGetHashCode()
często nie jest tego wart, lub nie jest to możliwe.Jeśli ktoś umieści Twój obiekt w kolekcji, która wywołuje,
GetHashCode()
a Ty przesłoniłeś,Equals()
nieGetHashCode()
zachowując się również we właściwy sposób, osoba ta może spędzić dni na śledzeniu problemu.Dlatego domyślnie tak.
źródło
GetHashCode
funkcji, tak że dowolne dwa równe obiekty zwracają ten sam kod skrótu;return 24601;
ireturn 8675309;
obie byłyby poprawnymi implementacjamiGetHashCode
. WydajnośćDictionary
będzie przyzwoita tylko wtedy, gdy liczba przedmiotów będzie niewielka, a pogorszy się, jeśli liczba przedmiotów się zwiększy, ale i tak będzie działać poprawnie.Wynika to z faktu, że struktura wymaga, aby dwa identyczne obiekty musiały mieć ten sam kod skrótu. Jeśli przesłonisz metodę równości, aby wykonać specjalne porównanie dwóch obiektów, a te dwa obiekty zostaną uznane przez metodę za identyczne, wówczas kod skrótu tych dwóch obiektów również musi być taki sam. (Słowniki i tabele skrótów opierają się na tej zasadzie).
źródło
Aby dodać powyższe odpowiedzi:
Jeśli nie zastąpisz opcji Równa się, domyślnym zachowaniem jest porównywanie odniesień do obiektów. To samo dotyczy hashcode - domyślna implementacja zwykle opiera się na adresie pamięci odniesienia. Ponieważ zastąpiłeś Equals, oznacza to, że poprawnym zachowaniem jest porównanie wszystkiego, co zaimplementowałeś w Equals, a nie referencji, więc powinieneś zrobić to samo dla kodu skrótu.
Klienci twojej klasy oczekują, że kod skrótu będzie miał logikę podobną do metody równości, na przykład metody linq, które używają IEqualityComparer najpierw porównają kody skrótu i tylko jeśli są równe, porównają metodę Equals (), która może być droższa aby uruchomić, jeśli nie zaimplementujemy hashcode, równy obiekt prawdopodobnie będzie miał różne hashcode (ponieważ mają inny adres pamięci) i zostanie błędnie określony jako nierówny (Equals () nawet nie trafi).
Ponadto, z wyjątkiem problemu polegającego na tym, że możesz nie być w stanie znaleźć swojego obiektu, jeśli używałeś go w słowniku (ponieważ został on wstawiony przez jeden kod skrótu, a gdy go szukasz, domyślny kod skrótu prawdopodobnie będzie inny i znowu będzie równy () nawet nie zostanie wywołany, jak wyjaśnia Marc Gravell w swojej odpowiedzi, wprowadzasz również naruszenie słownika lub koncepcji skrótu, które nie powinny dopuszczać identycznych kluczy - już zadeklarowałeś, że te obiekty są zasadniczo takie same, gdy przesłonisz Equals, więc nie nie chcę obu z nich jako różnych kluczy w strukturze danych, które mają mieć unikalny klucz, ale ponieważ mają one inny kod skrótu, „ten sam” klucz zostanie wstawiony jako inny.
źródło
Kod skrótu jest używany w kolekcjach opartych na skrócie, takich jak Dictionary, Hashtable, HashSet itp. Celem tego kodu jest bardzo szybkie wstępne sortowanie określonego obiektu przez umieszczenie go w określonej grupie (segmencie). To wstępne sortowanie ogromnie pomaga w znalezieniu tego obiektu, gdy trzeba go odzyskać z kolekcji mieszania, ponieważ kod musi wyszukiwać obiekt w jednym wiadrze zamiast we wszystkich obiektach w nim zawartych. Im lepsza dystrybucja kodów skrótu (lepsza unikalność), tym szybsze pobieranie. W idealnej sytuacji, w której każdy obiekt ma unikalny kod skrótu, znalezienie go jest operacją O (1). W większości przypadków zbliża się do O (1).
źródło
To niekoniecznie ważne; zależy to od wielkości twoich kolekcji i wymagań dotyczących wydajności oraz od tego, czy Twoja klasa będzie używana w bibliotece, w której możesz nie znać wymagań dotyczących wydajności. Często wiem, że moje rozmiary kolekcji nie są bardzo duże, a mój czas jest cenniejszy niż kilka mikrosekund wydajności osiągniętej dzięki stworzeniu idealnego kodu skrótu; więc (aby pozbyć się irytującego ostrzeżenia kompilatora) po prostu używam:
(Oczywiście, mógłbym użyć #pragmy również do wyłączenia ostrzeżenia, ale wolę w ten sposób.)
Po pojawieniu się w położeniu, które nie potrzebują wydajność niż wszystkich zagadnień wymienionych przez innych tutaj zastosować, oczywiście. Najważniejsze - w przeciwnym razie przy pobieraniu elementów z zestawu skrótów lub słownika wystąpią błędne wyniki: kod skrótu nie może różnić się w zależności od czasu życia obiektu (a dokładniej w czasie, gdy potrzebny jest kod skrótu, na przykład podczas bycia klucz w słowniku): na przykład poniższe informacje są niepoprawne, ponieważ wartość jest publiczna i dlatego można ją zmienić zewnętrznie na klasę w czasie istnienia instancji, więc nie wolno jej używać jako podstawy kodu skrótu:
Z drugiej strony, jeśli nie można zmienić wartości, można użyć:
źródło
Zawsze powinieneś gwarantować, że jeśli dwa obiekty są równe, zgodnie z definicją Equals (), powinny zwrócić ten sam kod skrótu. Jak twierdzą niektóre inne komentarze, teoretycznie nie jest to obowiązkowe, jeśli obiekt nigdy nie będzie używany w kontenerze opartym na haszowaniu, takim jak HashSet lub Dictionary. Radziłbym ci jednak zawsze przestrzegać tej zasady. Powodem jest po prostu to, że zbyt łatwo jest komuś zmienić kolekcję z jednego typu na inny z dobrym zamiarem faktycznej poprawy wydajności lub po prostu lepszego przekazania semantyki kodu.
Załóżmy na przykład, że przechowujemy niektóre obiekty na liście. Jakiś czas później ktoś faktycznie zdaje sobie sprawę, że HashSet jest znacznie lepszą alternatywą, na przykład ze względu na lepszą charakterystykę wyszukiwania. To wtedy możemy wpaść w kłopoty. Lista wewnętrznie użyje domyślnego modułu porównującego równość dla typu, co w twoim przypadku oznacza Equals, podczas gdy HashSet korzysta z GetHashCode (). Jeśli oba zachowują się inaczej, twój program również. I pamiętaj, że takie problemy nie są najłatwiejsze do rozwiązania.
Podsumowałem to zachowanie przy pomocy innych pułapek GetHashCode () w poście na blogu, gdzie można znaleźć dalsze przykłady i wyjaśnienia.
źródło
Jako
.NET 4.7
preferowaną metodę zastępowaniaGetHashCode()
pokazano poniżej. Jeśli celujesz w starsze wersje .NET, dołącz pakiet nuget System.ValueTuple .Pod względem wydajności ta metoda przewyższy większość implementacji złożonego kodu mieszającego. ValueTuple jest
struct
tak, że nie będzie żadnych śmieci, a algorytm bazowy jest tak szybko, jak to tylko możliwe.źródło
Rozumiem, że pierwotna funkcja GetHashCode () zwraca adres pamięci obiektu, więc konieczne jest zastąpienie go, jeśli chcesz porównać dwa różne obiekty.
ZMIENIONO: To było niepoprawne, oryginalna metoda GetHashCode () nie może zapewnić równości 2 wartości. Jednak równe obiekty zwracają ten sam kod skrótu.
źródło
Poniżej użycie refleksji wydaje mi się lepszą opcją biorąc pod uwagę właściwości publiczne, ponieważ dzięki temu nie musisz się martwić o dodawanie / usuwanie właściwości (choć nie tak powszechny scenariusz). Okazało się, że również działa lepiej (w porównaniu do czasu przy użyciu stopera Diagonistics).
źródło