GetHashCode Guidelines in C#

Czytałem w książce Essential C# 3.0 i. NET 3.5, że:

Zwraca GetHashCode () w ciągu życia danego obiektu powinny być stała (ta sama wartość), nawet jeśli DANE obiektu ulegną zmianie. W wielu przypadki, powinieneś buforować metodę return, aby to wymusić.

Czy jest to ważna wskazówka?

Próbowałem kilku wbudowanych typów w. NET i nie zachowywały się tak.

Author: Michał Powaga, 2009-01-20

9 answers

Odpowiedź brzmi w większości: jest to ważna wskazówka, ale być może nie jest to ważna reguła. To również nie opowiada całej historii.

Chodzi o to, że dla typów zmiennych, nie można oprzeć kodu hash na zmiennych danych, ponieważ dwa równe obiekty muszą zwrócić ten sam kod hash, a kod hash musi być ważny przez cały okres życia obiektu. Jeśli kod skrótu ulegnie zmianie, zostanie znaleziony obiekt, który zostanie utracony w kolekcji haszowanej, ponieważ nie żyje już w prawidłowym haszu bin.

Na przykład obiekt A zwraca hash równy 1. Więc idzie do Kosza 1 z tabeli hash. Następnie zmieniasz obiekt A tak, że zwraca hash 2. Gdy tabela hash szuka go, szuka w bin 2 i nie może go znaleźć - obiekt jest osierocony w bin 1. To dlatego kod hashowy nie może się zmieniać przez cały czas życia obiektu , a jednym z powodów, dla których pisanie implementacji GetHashCode jest wrzód na tyłku.

Aktualizacja
Eric Lippert opublikował blog , który daje doskonałe informacje na temat GetHashCode.

Dodatkowa Aktualizacja
Zrobiłem kilka zmian powyżej:

  1. dokonałem rozróżnienia między wytycznymi a regułami.
  2. przeszedłem przez "na całe życie obiektu".

Wytyczna jest tylko przewodnikiem, a nie regułą. W rzeczywistości GetHashCode musi postępować zgodnie z tymi wytycznymi tylko wtedy, gdy rzeczy oczekują, że obiekt będzie postępować zgodnie z wytycznymi, na przykład gdy jest przechowywany w tabeli skrótów. Jeśli nigdy nie zamierzasz używać swoich obiektów w tabelach hash (lub czegokolwiek innego, co opiera się na regułach GetHashCode), twoja implementacja nie musi postępować zgodnie z wytycznymi.

Kiedy zobaczysz "na czas życia obiektu", powinieneś przeczytać "na czas, przez który obiekt musi współpracować z tabelami hash" lub podobnym. Jak większość rzeczy, GetHashCode polega na tym, aby wiedzieć, kiedy łamać zasady.

 89
Author: Jeff Yates,
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
2014-05-06 16:23:28

Minęło dużo czasu, ale mimo to uważam, że nadal należy udzielić poprawnej odpowiedzi na to pytanie, w tym wyjaśnić dlaczego i jak. Jak na razie najlepszą odpowiedzią jest ta, która wyczerpująco cytuje MSDN - nie próbuj tworzyć własnych zasad, chłopaki z MS wiedzieli co robią.

Ale po kolei: Wytyczna przytoczona w pytaniu jest błędna.

Teraz dlaczego-jest ich dwóch

Pierwszy dlaczego : Jeśli hashcode jest obliczany w taki sposób, że nie zmienia się w ciągu życia obiektu, nawet jeśli sam obiekt się zmienia, to złamie umowę equals-contract.

Remember: "Jeśli dwa obiekty są równe, metoda GetHashCode dla każdego obiektu musi zwrócić tę samą wartość. Jeśli jednak dwa obiekty nie są sobie równe, metody GetHashCode dla tych dwóch obiektów nie muszą zwracać różnych wartości."

Drugie zdanie często jest błędnie interpretowane jako " jedyna zasada jest taka, że przy czas tworzenia, hashcode równych obiektów musi być równy". Naprawdę Nie wiem dlaczego, ale to jest o istotę większości odpowiedzi tutaj, jak również.

Pomyśl o dwóch obiektach zawierających nazwę, gdzie nazwa jest używana w metodzie equals: Same name - > same thing. Create Instance A: Name = Joe Utwórz Instancję B: Name = Peter

Hashcode A i Hashcode B najprawdopodobniej nie będą takie same. Co się stanie, gdy nazwa instancji B zostanie zmieniona na Joe?

Zgodnie z wytyczna z pytania, hashcode B nie zmieni. Rezultatem tego będzie: A. Equals (B) = = > true Ale w tym samym czasie: A. GetHashCode () = = B. GetHashCode () = = > false.

Ale dokładnie to zachowanie jest zabronione przez equals & hashcode-contract.

Drugie dlaczego : Chociaż jest to - oczywiście-prawda, że zmiany w hashcode mogą złamać zaszyfrowane listy i inne obiekty za pomocą hashcode, odwrotna jest również prawda. Nie zmieniając hashcode będzie w w najgorszym przypadku get hashed lists, gdzie wiele różnych obiektów będzie miało ten sam hashcode i dlatego będzie w tym samym hash bin-dzieje się, gdy obiekty są inicjalizowane standardową wartością, na przykład.


Now coming to the hows Cóż, na pierwszy rzut oka wydaje się, że istnieje sprzeczność - tak czy inaczej, kod pęknie. Ale żaden problem nie pochodzi ze zmienionego lub niezmienionego hashcode.

Źródło problemów jest dobrze opisane w MSDN:

Od Wpis hashtable MSDN:

Kluczowe obiekty muszą być niezmienne tak długo, jak długo ponieważ są one używane jako klucze w Hashtable.

To znaczy:

Każdy obiekt, który tworzy hashvalue, powinien zmienić hashvalue, gdy obiekt się zmienia, ale nie może - absolutnie nie może - pozwolić na jakiekolwiek zmiany w sobie, gdy jest używany wewnątrz Hashtable(lub jakiegokolwiek innego obiektu używającego Hash, oczywiście).

Najpierw jak Najprostszym sposobem byłoby oczywiście zaprojektowanie obiektów niezmiennych tylko do użytku w hashtablach, które będą tworzone jako kopie normalnych, mutowalnych obiektów w razie potrzeby. Wewnątrz niezmiennych obiektów jest oczywiście w porządku buforować hashcode, ponieważ jest niezmienny.

Drugi jak Lub nadaj obiektowi flagę "jesteś teraz zahaszowany", upewnij się, że wszystkie dane obiektu są prywatne, sprawdź flagę we wszystkich funkcjach, które mogą zmieniać dane obiektów i rzucaj dane wyjątku, jeśli zmiana nie jest dozwolona (tzn. flaga jest ustawiona). Teraz, gdy umieścisz obiekt w dowolnym zaszyfrowanym obszarze, wykonaj pamiętaj, aby ustawić flagę i - jak również-odłączyć flagę, gdy nie jest już potrzebna. Dla ułatwienia radzę ustawić flagę automatycznie wewnątrz metody "GetHashCode" - w ten sposób nie można jej zapomnieć. A jawne wywołanie metody "ResetHashFlag" sprawi, że programista będzie musiał pomyśleć, czy do tej pory nie może zmieniać danych obiektów.

Ok, co też należy powiedzieć: są przypadki, w których możliwe jest posiadanie obiektów z zmiennymi danymi, gdzie hashcode jest jednak niezmieniony, gdy dane obiektów są zmieniane, bez naruszania równości & hashcode-contract.

Wymaga to jednak, aby metoda equals-nie opierała się również na zmiennych danych. Tak więc, jeśli napiszę obiekt i stworzę metodę GetHashCode, która obliczy wartość tylko raz i zapisze ją wewnątrz obiektu, aby zwrócić ją przy późniejszych wywołaniach, to ponownie muszę: absolutnie muszę, utworzyć metodę Equals, która użyje przechowywanych wartości do porównania, tak, że A. równa się (B) nigdy nie zmieni się również z false na true. W przeciwnym razie umowa zostałaby zerwana. Rezultatem tego będzie zazwyczaj to, że metoda Equals nie ma żadnego sensu - nie jest to oryginalne odniesienie equals, ale nie jest to również wartość equals. Czasami może to być zachowanie zamierzone( np. zapisy klientów), ale zazwyczaj tak nie jest.

Więc po prostu zmień wynik GetHashCode, gdy zmienią się DANE obiektu i jeśli użycie obiektu wewnątrz hasha za pomocą listy lub obiekty są przeznaczone (lub po prostu możliwe), a następnie sprawiają, że obiekt jest niezmienny lub tworzą flagę readonly, której można używać przez cały okres istnienia haszowanej listy zawierającej obiekt.

(przy okazji: wszystko to nie jest specyficzne dla C# oder. NET - to w naturze wszystkich implementacji hashtable, a bardziej ogólnie każdej indeksowanej listy, leży to, że dane identyfikacyjne obiektów nigdy nie powinny się zmieniać, podczas gdy obiekt znajduje się na liście. Nieoczekiwane i nieprzewidywalne zachowanie nastąpi, jeśli ta reguła zostanie złamana. Gdzieś mogą być implementacje list, które monitorują wszystkie elementy wewnątrz listy i automatycznie ją reindeksują - ale ich wydajność na pewno będzie makabryczna.)

 119
Author: Alex,
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
2010-07-13 08:53:24

From MSDN

Jeśli dwa obiekty są równe, to Metoda GetHashCode dla każdego obiektu musi zwrócić tę samą wartość. Jednakże, jeśli dwa obiekty nie porównują się jako równe, metody GetHashCode dla dwa obiekty nie muszą wracać różne wartości.

Metoda GetHashCode dla obiektu musi stale zwracać ten sam hash kod tak długo, jak nie ma modyfikacja obiektu stanowi, że określa wartość zwracaną metoda Equals obiektu. Zauważ, że to jest prawdziwe tylko dla bieżącego wykonania wniosku, oraz że a można zwrócić inny kod hashowy, jeśli aplikacja jest uruchamiana ponownie.

Dla najlepszej wydajności, hash funkcja musi generować losowy Dystrybucja dla wszystkich wejść.

Oznacza to, że jeśli wartość(wartości) obiektu ulegnie zmianie, kod skrótu powinien się zmienić. Na przykład klasa "Person" z właściwością" Name "ustawioną na" Tom " powinna mieć jeden kod hashowy i inny kod, jeśli zmienisz nazwę na "Jerry". W przeciwnym razie, Tom = = Jerry, co prawdopodobnie nie jest tym, co zamierzałeś.


Edit :

Również z MSDN:

Klasy pochodne, które nadpisują GetHashCode muszą również nadpisać Equals, aby zagwarantować, że dwa obiekty uznane za równe mają ten sam kod skrótu; w przeciwnym razie Typ Hashtable może nie działać poprawnie.

Z wpis hashtable MSDN :

Kluczowe obiekty muszą być niezmienne, o ile są używane jako klucze w Hashtable.

Sposób, w jaki czytałem to jest to, że mutable objects powinnyzwracać różne hashcody w miarę zmiany ich wartości, chyba że są przeznaczone do użycia w hashtable.

W przykładzie systemu.Rysunek. zwraca inny hashcode, gdy zmienia się wartość X lub Y. To uczyniłoby go słabym kandydatem do wykorzystania jako-jest w hashtable.

 9
Author: Jon B,
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-01-20 19:22:13

Myślę, że dokumentacja dotycząca GetHashcode jest nieco myląca.

Z jednej strony MSDN stwierdza, że hashcode obiektu nigdy nie powinien się zmieniać i być stały Z drugiej strony, MSDN stwierdza również, że zwracana wartość GetHashcode powinna być równa dla 2 obiektów, jeśli te 2 obiekty są uważane za równe.

MSDN:

Funkcja hash musi mieć następujące właściwości:

  • Jeśli dwa obiekty są równe, to Metoda GetHashCode dla każdego obiektu musi zwrócić tę samą wartość. Jednakże, jeśli dwa obiekty nie porównują się jako równe, metody GetHashCode dla dwa obiekty nie muszą wracać różne wartości.
  • metoda GetHashCode dla obiektu musi konsekwentnie zwracać ten sam kod hashowy, o ile nie ma modyfikacja obiektu stanowi, że określa wartość zwracaną metoda Equals obiektu. Zauważ, że to jest prawdziwe tylko dla bieżącego wykonania na aplikacji, oraz że a można zwrócić inny kod hashowy, jeśli aplikacja jest uruchamiana ponownie.
  • aby uzyskać najlepszą wydajność, funkcja hash musi wygenerować losowy Dystrybucja dla wszystkich wejść.

Oznacza to, że wszystkie twoje obiekty powinny być niezmienne, lub metoda GetHashcode powinna być oparta na właściwościach Twojego obiektu, które są niezmienne. Załóżmy na przykład, że masz tę klasę (implementację naiwną):

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

Ta implementacja już narusza zasady, które można znaleźć w MSDN. Załóżmy, że masz 2 instancje tej klasy; właściwość Name instance1 jest ustawiona na "Pol", a właściwość Name instance2 jest ustawiona na "Piet". Obie instancje zwracają inny hashcode, a także nie są równe. Załóżmy, że zmienię nazwę instance2 na 'Pol', wtedy, zgodnie z moją metodą Equals, obie instancje powinny być równe i zgodnie z jedną z reguł MSDN powinny zwracać ten sam hashcode.
Jednakże, nie można tego zrobić, ponieważ hashcode instance2 ulegnie zmianie, a MSDN stwierdza, że nie jest to dozwolone.

Następnie, jeśli masz encję, możesz zaimplementować hashcode tak, aby używał 'podstawowego identyfikatora' tego encji, który może być idealnym kluczem zastępczym lub niezmienną właściwością. Jeśli masz obiekt value, możesz zaimplementować Hashcode tak, aby używał on 'właściwości' tego obiektu value. Te właściwości składają się na "definicję" obiektu value. To jest oczywiście charakter obiektu wartości; nie interesuje cię jego tożsamość, ale raczej jego wartość.
I dlatego obiekty wartości powinny być niezmienne. (Tak jak w. NET framework, string, Date, itp... są obiektami niezmiennymi).

Kolejna rzecz, która przychodzi na myśl:
Podczas której 'sesja' (Nie wiem jak to nazwać) powinna' GetHashCode ' zwracać stałą wartość. Załóżmy, że otwierasz swoją aplikację, ładujesz instancję obiektu z DB (encja), i uzyskać jego hashcode. Zwróci określoną liczbę. Zamknij aplikację i załaduj ten sam obiekt. Czy wymagane jest, aby hashcode tym razem miał taką samą wartość, jak podczas ładowania encji po raz pierwszy ? IMHO nie.

 9
Author: Frederik Gheysels,
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
2010-08-10 13:17:18

To dobra rada. Oto co Brian Pepin ma do powiedzenia w tej sprawie:

To mnie poruszyło bardziej niż raz: Upewnij się, że GetHashCode zawsze zwraca tę samą wartość przez dożywotnia instancja. Pamiętaj, że kody hash służą do identyfikacji "wiadra" w większości wdrożenia. Jeśli obiekt jest zmiany "wiadra", hashtable może nie być w stanie znaleźć swój obiekt. Mogą one być bardzo trudne do znalezienia, więc dostać to racja za pierwszym razem.

 8
Author: Justin R.,
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-01-20 18:37:35

Nie odpowiada bezpośrednio na twoje pytanie, ale-jeśli używasz Resharper, nie zapominaj, że ma funkcję, która generuje dla Ciebie rozsądną implementację GetHashCode (a także metodę Equals). Możesz oczywiście określić, którzy członkowie klasy będą brani pod uwagę przy obliczaniu hashcode.

 5
Author: petr k.,
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-01-20 18:35:22

Zobacz ten wpis na blogu od marca Brooksa:

VTOs, RTOs i GetHashCode () -- o rany!

A następnie sprawdź follow up post (nie mogę linkować, ponieważ jestem nowy, ale jest link w artykule initlal), który omawia dalej i obejmuje kilka drobnych niedociągnięć w początkowej implementacji.

To było wszystko, co musiałem wiedzieć o tworzeniu implementacji GetHashCode (), on nawet zapewnia pobieranie swojej metody wraz z innymi narzędziami, w skrócie złoto.

 5
Author: Shaun,
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
2010-02-19 10:35:24

Hashcode nigdy się nie zmienia, ale ważne jest również, aby zrozumieć, skąd pochodzi Hashcode.

Jeśli twój obiekt używa semantyki wartości, tzn. tożsamość obiektu jest określona przez jego wartości (np. String, Color, all structs). Jeśli tożsamość obiektu jest niezależna od wszystkich jego wartości, to Hashcode jest identyfikowany przez podzbiór jego wartości. Na przykład wpis Stoskoverflow jest przechowywany gdzieś w bazie danych. Jeśli zmienisz imię i nazwisko lub adres e-mail, wpis klienta pozostaje bez zmian, chociaż niektóre wartości uległy zmianie (ostatecznie zazwyczaj identyfikuje cię jakiś długi identyfikator klienta#).

W skrócie:

Semantyka typu wartości-Hashcode jest definiowany wartościami Semantyka typu odniesienia-Hashcode jest zdefiniowany przez jakiś id

Proponuję przeczytać Domain Driven Design autorstwa Erica Evansa, gdzie przechodzi do encji vs typów wartości (co jest mniej więcej tym, co próbowałem zrobić powyżej), jeśli to nadal nie ma sensu.

 4
Author: DavidN,
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-01-21 15:01:19

Sprawdź wytyczne i zasady GetHashCode Eric Lippert

 3
Author: Ian Ringrose,
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
2014-05-06 16:22:49