Nadpisanie kodu GetHashCode dla mutable objects?

Czytałem o 10 różnych pytaniach na temat tego, kiedy i jak obejść GetHashCode ale wciąż jest coś, czego nie rozumiem. Większość implementacji GetHashCode opiera się na kodach hashowych pól obiektu, ale przytoczono, że wartość GetHashCode nigdy nie powinna się zmieniać w ciągu życia obiektu. Jak to działa, jeśli pola, na których się opiera, są zmienne? Co też, jeśli chcę, aby wyszukiwania słownikowe itp. były oparte na równości referencji, a nie na moim nadpisanym Equals?

Przede wszystkim nadpisuję Equals dla łatwości testowania jednostkowego mojego kodu serializacyjnego, który zakładam serializowanie i deserializowanie (w moim przypadku do XML) zabija równość odniesienia, więc chcę się upewnić, że przynajmniej jest poprawna przez równość wartości. Czy to zła praktyka, aby zastąpić Equals w tym przypadku? Zasadniczo w większości kodu wykonującego chcę równości referencji i zawsze używam == i nie nadpisuję tego. Czy powinienem stworzyć nową metodę ValueEquals czy coś innego zamiast / align = "left" / Kiedyś zakładałem, że framework zawsze używa ==, a nie Equals do porównywania rzeczy, więc pomyślałem, że bezpieczne jest obejście Equals, ponieważ wydawało mi się, że jego celem jest, jeśli chcesz mieć drugą definicję równości, która różni się od operatora ==. Z lektury kilku innych pytań, choć wydaje się, że tak nie jest.

EDIT:

Wydaje się, że moje intencje były niejasne, chodzi mi o to, że 99% czasu chcę zwykłej starości równość odniesienia, domyślne zachowanie, bez niespodzianek. W bardzo rzadkich przypadkach chcę mieć równość wartości i chcę wyraźnie zażądać równości wartości za pomocą .Equals zamiast ==.

Kiedy to robię, kompilator zaleca, abym nadpisał GetHashCode, i tak pojawiło się to pytanie. Wydaje się, że istnieją sprzeczne cele dla GetHashCode, gdy są stosowane do obiektów mutowalnych, są to:

  1. If a.Equals(b) then a.GetHashCode() should == b.GetHashCode().
  2. wartość a.GetHashCode() nie powinno się zmieniać przez cały okres życia a.

Wydają się naturalnie sprzeczne, gdy zmienny obiekt, ponieważ jeśli stan obiektu się zmieni, oczekujemy zmiany wartości .Equals(), co oznacza, że GetHashCode powinna się zmienić, aby dopasować się do zmiany w .Equals(), Ale GetHashCode nie powinna się zmienić.

Dlaczego wydaje się, że istnieje ta sprzeczność? Czy te zalecenia nie mają na celu zastosowania do mutowalnych obiektów? Prawdopodobnie zakładane, ale może warto wspomnieć, że mam na myśli klasy, a nie struktury.

Rozdzielczość:

Zaznaczam JaredPar jako zaakceptowany, ale głównie za komentarze. Podsumowując, nauczyłem się z tego, że jedynym sposobem na osiągnięcie wszystkich celów i uniknięcie możliwych dziwacznych zachowań w skrajnych przypadkach jest nadpisanie Equals i GetHashCode Na podstawie niezmiennych pól lub zaimplementowanie IEquatable. Ten rodzaj wydaje się zmniejszać przydatność nadrzędnego Equals dla typów referencyjnych, ponieważ z tego, co widziałem większość typów referencyjnych zwykle nie mają niezmiennych pól, chyba że są przechowywane w relacyjnej bazie danych, aby zidentyfikować je za pomocą kluczy głównych.

Author: Wai Ha Lee, 2009-05-17

5 answers

Jak to działa, jeśli pola, na których się opiera, są zmienne?

Nie w tym sensie, że kod skrótu zmieni się wraz ze zmianą obiektu. Jest to problem ze wszystkich powodów wymienionych w czytanych artykułach. Niestety jest to rodzaj problemu, który zazwyczaj pojawia się tylko w narożnych przypadkach. Więc deweloperzy mają tendencję do unikania złego zachowania.

Również co jeśli chcę, aby wyszukiwania słownika itp były oparte na równości referencji, a nie na moim overridden Równi?

Dopóki zaimplementujesz interfejs taki jak {[0] } nie powinno to być problemem. Większość implementacji słownikowych wybierze porównywarkę równości w sposób, który użyje IEquatable<T> nad obiektem./ Align = "left" / Nawet bez IEquatable<T>, większość domyślnie wywoła obiekt.Equals (), które następnie trafią do Twojej implementacji.

Zasadniczo w większości kodu wykonującego chcę równości referencji i zawsze używam == i nie nadpisuję tego.

Jeśli oczekujesz swoich obiektów aby zachować równość wartości należy nadpisać = = and != aby wymusić równość wartości dla wszystkich porównań. Użytkownicy nadal mogą używać obiektu.ReferenceEquals, jeśli rzeczywiście chcą równości referencji.

Kiedyś zakładałem, że framework zawsze używa ==, a nie jest równy do porównywania rzeczy

To, czego używa BCL, zmieniło się trochę w czasie. Teraz większość przypadków, które używają równości, bierze instancję IEqualityComparer<T> i używa jej do równości. W przypadkach, gdy jeden nie jest określony, użyją EqualityComparer<T>.Default żeby znaleźć. W najgorszym przypadku domyślnie wywoła obiekt.Equals

 20
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-17 01:07:07

Jeśli masz zmienny obiekt, nie ma sensu nadpisywać metody GetHashCode, ponieważ nie możesz jej użyć. Jest używany na przykład przez Kolekcje Dictionary i HashSet, aby umieścić każdy element w wiadrze. Jeśli zmienisz Obiekt, gdy jest używany jako klucz w kolekcji, kod skrótu nie będzie już pasował do zasobnika, w którym znajduje się obiekt, więc kolekcja nie będzie działać poprawnie i możesz już nigdy go nie znaleźć.

Jeśli chcesz, aby wyszukiwanie nie używało GetHashCode lub Equals metoda klasy, zawsze możesz podać własną implementację IEqualityComparer do użycia zamiast niej podczas tworzenia Dictionary.

Metoda Equals jest przeznaczona do równości wartości, więc nie jest źle zaimplementować ją w ten sposób.

 6
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-17 01:21:55

Wow, to właściwie kilka pytań w jednym : -). Więc jeden po drugim:

Przytoczono, że wartość GetHashCode nigdy nie powinna się zmieniać w ciągu życia obiektu. Jak to działa, jeśli pola, na których się opiera, są zmienne?

Ta powszechna rada jest przeznaczona dla Przypadku, Gdy chcesz użyć obiektu jako klucza w HashTable / słowniku itp. . Hashtable zwykle wymagają, aby hash nie zmieniał się, ponieważ używają go do decydowania o tym, jak przechowywać & Odzyskaj klucz. Jeśli hash ulegnie zmianie, tablica HashTable prawdopodobnie nie będzie już znajdować Twojego obiektu.

Aby przytoczyć dokumenty interfejsu Javy Map :

Uwaga: Należy zachować szczególną ostrożność, jeśli zmienne obiekty są używane jako klucze map. Zachowanie mapy nie jest określone, jeśli wartość obiektu zostanie zmieniona w sposób wpływający na porównania równości, podczas gdy obiekt jest kluczem na mapie.

Ogólnie rzecz biorąc, używanie dowolnego rodzaju mutowalnego obiektu jest złym pomysłem jako klucz w tabeli hash: nie jest nawet jasne, co powinno się stać, jeśli klucz zmieni się po dodaniu go do tabeli hash. Czy tabela hash powinna zwracać przechowywany obiekt za pomocą starego klucza, nowego klucza lub obu?

Tak więc prawdziwa rada brzmi: używaj tylko niezmiennych obiektów jako kluczy i upewnij się, że ich hashcode nigdy się nie zmienia (co zwykle jest automatyczne, jeśli obiekt jest niezmienny).

Również co, jeśli chcę, aby wyszukiwania słownika itp były oparte na równości odniesienia nie Moje przesadne równo?

Cóż, znajdź implementację słownika, która tak działa. Ale słowniki biblioteki standardowej używają hashcode & Equals i nie ma sposobu, aby to zmienić.

Przede wszystkim nadpisuję Equals dla łatwości testowania jednostkowego mojego kodu serializacyjnego, który zakładam serializing i deserializing (do XML w moim przypadku) zabija równość odniesienia, więc chcę się upewnić, że przynajmniej jest poprawna przez równość wartości. Czy to zła praktyka, aby obejść Równi w tym przypadku?

Nie, uznałbym to za całkowicie akceptowalne. Nie należy jednak używać takich obiektów jak klucze w słowniku/hashtable, ponieważ można je zmieniać. Patrz wyżej.

 3
Author: sleske,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-17 12:25:24

Tematem przewodnim jest jak najlepiej identyfikować obiekty. Wspominasz serializację/deserializację, która jest ważna, ponieważ integralność odniesienia jest tracona w tym procesie.

Krótka odpowiedź jest taka, że obiekty powinny być jednoznacznie identyfikowane przez najmniejszy zestaw niezmiennych pól, które mogą być używane do tego celu. Są to pola, których powinieneś użyć, gdy nadpisujesz GetHashCode i Equals.

Do testowania jest całkiem sensowne definiowanie dowolnych twierdzeń need, zazwyczaj nie są one definiowane na samym typie, ale raczej jako metody użytkowe w zestawie testowym. Może TestSuite.AssertEquals (MyClass, MyClass) ?

Zwróć uwagę, że GetHashCode i Equals powinny działać razem. GetHashCode powinien zwracać tę samą wartość dla dwóch obiektów, jeśli są sobie równe. Equals powinien zwracać true wtedy i tylko wtedy, gdy dwa obiekty mają ten sam kod skrótu. (Zauważ, że możliwe, że dwa obiekty nie są sobie równe, ale mogą zwrócić ten sam kod skrótu). Jest mnóstwo strona, która zajmuje się tym tematem head-on, wystarczy google away.

 1
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-17 01:35:10

Nie wiem, czy C# jest względnym noobem, ale w Javie, jeśli nadpisujesz equals (), musisz również nadpisać hashCode (), aby utrzymać kontrakt między nimi (i vice-versa)... A java też ma ten sam catch 22; W zasadzie wymuszanie używania pól niezmiennych... Ale jest to problem tylko dla klas, które są używane jako klucz hashowy, a Java ma alternatywne implementacje dla wszystkich kolekcji opartych na hashach... które może nie tak szybko, ale skutecznie pozwalają na użycie mutowalnego obiektu jako klucz... po prostu (zazwyczaj) marszczy brwi jako "kiepski projekt".

I czuję potrzebę zwrócenia uwagi, że ten podstawowy problem jest ponadczasowy... Jest tu od kiedy Adam był chłopcem.

Pracowałem nad kodem fortran, który jest starszy ode mnie (mam 36 lat), który psuje się po zmianie nazwy użytkownika (np. gdy dziewczyna wychodzi za mąż lub się rozwiedzie ;-) ... Tak więc jest inżynieria, przyjęte rozwiązanie było: GetHashCode "metoda" zapamiętuje wcześniej obliczony hashCode, przelicza hashCode (tzn. wirtualny znacznik isDirty) i jeśli pola kluczowe zostały zmienione, zwraca null. Powoduje to, że cache usunie "brudnego" użytkownika (przez wywołanie innego GetPreviousHashCode), a następnie Cache zwróci null, powodując ponowne odczytanie z bazy danych. Ciekawy i wartościowy hack; nawet jeśli sam tak powiem; -)

Wymienię zmienność (pożądaną tylko w narożnych przypadkach) na dostęp O(1) (pożądaną we wszystkich przypadkach). Witamy w engineering; krainie świadomych kompromis.

Zdrówko. Keith.

 1
Author: corlettk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-17 06:57:17