Minęło dużo czasu, ale myślę, że nadal konieczne jest udzielenie poprawnej odpowiedzi na to pytanie, w tym wyjaśnień, dlaczego i jak. Jak dotąd najlepszą odpowiedzią jest ta, która wyczerpująco przytacza MSDN - nie próbuj tworzyć własnych reguł, ludzie z MS wiedzieli, co robią.
Ale najpierw sprawa: Wytyczne cytowane w pytaniu są błędne.
A teraz dlaczego - jest ich dwóch
Po pierwsze, dlaczego : Jeśli hashcode jest obliczany w taki sposób, że nie zmienia się w czasie życia obiektu, nawet jeśli sam obiekt się zmienia, to zrywałoby to kontrakt równości.
Pamiętaj: „Jeśli dwa obiekty są porównywane jako równe, metoda GetHashCode dla każdego obiektu musi zwracać tę samą wartość. Jeśli jednak dwa obiekty nie są porównywane jako równe, metody GetHashCode dla dwóch obiektów nie muszą zwracać różnych wartości”.
Drugie zdanie jest często błędnie interpretowane jako „Jedyną zasadą jest to, że w czasie tworzenia obiektu hashcode równych obiektów musi być równy”. Naprawdę nie wiem dlaczego, ale to jest także istota większości odpowiedzi tutaj.
Pomyśl o dwóch obiektach zawierających nazwę, których nazwa jest używana w metodzie equals: Ta sama nazwa -> ta sama rzecz. Utwórz instancję A: Imię = Joe Utwórz instancję B: Imię = Piotr
Hashcode A i Hashcode B najprawdopodobniej nie będą takie same. Co by się stało, gdyby nazwa instancji B została zmieniona na Joe?
Zgodnie z wytyczną z pytania, kod skrótu B nie ulegnie zmianie. Wynikiem tego byłoby: A.Equals (B) ==> true Ale w tym samym czasie: A.GetHashCode () == B.GetHashCode () ==> false.
Ale dokładnie to zachowanie jest wyraźnie zabronione przez kontrakt equals & hashcode.
Po drugie : chociaż jest - oczywiście - prawdą, że zmiany w kodzie skrótu mogą uszkodzić listy zaszyfrowane i inne obiekty korzystające z kodu skrótu, prawdą jest również odwrotna sytuacja. Brak zmiany kodu skrótu spowoduje w najgorszym przypadku zahaszowane listy, w których wiele różnych obiektów będzie miało ten sam kod skrótu i dlatego będzie znajdować się w tym samym koszu - ma to miejsce, gdy na przykład obiekty są inicjowane ze standardową wartością.
A teraz dochodzę do pytań Cóż, na pierwszy rzut oka wydaje się, że jest sprzeczność - tak czy inaczej, kod się zepsuje. Ale żaden problem nie wynika ze zmienionego lub niezmienionego hashcode.
Źródło problemów jest dobrze opisane w MSDN:
Z pozycji hashtable MSDN:
Kluczowe obiekty muszą być niezmienne, o ile są używane jako klucze w tablicy z haszowaniem.
To znaczy:
Każdy obiekt, który tworzy wartość skrótu, powinien zmienić wartość skrótu, gdy zmienia się obiekt, ale nie może - absolutnie nie może - zezwalać na jakiekolwiek zmiany w sobie samym, gdy jest używany wewnątrz tablicy z haszowaniem (lub oczywiście dowolnego innego obiektu używającego skrótu) .
Po pierwsze, jak najłatwiej byłoby oczywiście zaprojektować niezmienne obiekty tylko do użytku w tabelach skrótów, które w razie potrzeby będą tworzone jako kopie normalnych, zmiennych obiektów. Wewnątrz niezmiennych obiektów można oczywiście buforować kod skrótu, ponieważ jest on niezmienny.
Drugi sposób Lub nadaj obiektowi flagę „jesteś teraz zaszyfrowany”, upewnij się, że wszystkie dane obiektu są prywatne, sprawdź flagę we wszystkich funkcjach, które mogą zmieniać dane obiektu i wyrzuć dane wyjątku, jeśli zmiana jest niedozwolona (np. Flaga jest ustawiona ). Teraz, kiedy umieścisz obiekt w jakimkolwiek zakodowanym obszarze, upewnij się, że ustawiłeś flagę i - także - odznacz flagę, gdy nie jest już potrzebna. Dla ułatwienia radziłbym ustawić flagę automatycznie w metodzie „GetHashCode” - w ten sposób nie można o tym zapomnieć. A jawne wywołanie metody „ResetHashFlag” upewni się, że programista będzie musiał pomyśleć, czy ma, czy nie ma prawa zmieniać danych obiektu.
Ok, co też należy powiedzieć: są przypadki, w których możliwe jest posiadanie obiektów ze zmiennymi danymi, w których hashcode jest mimo to niezmieniony, gdy dane obiektów są zmieniane, bez naruszenia kontraktu equals & hashcode.
Wymaga to jednak, aby metoda równości nie była również oparta na zmiennych danych. Tak więc, jeśli napiszę obiekt i utworzę metodę GetHashCode, która oblicza wartość tylko raz i przechowuje ją wewnątrz obiektu, aby zwrócić ją w późniejszych wywołaniach, muszę ponownie: absolutnie muszę utworzyć metodę Equals, która będzie używać przechowywane wartości do porównania, aby A.Equals (B) również nigdy nie zmieniło się z fałszu na prawdę. W przeciwnym razie umowa zostałaby zerwana. Rezultatem tego będzie zwykle to, że metoda Equals nie ma żadnego sensu - to nie jest oryginalne odniesienie równe, ale nie jest to również wartość równa. Czasami może to być zamierzone zachowanie (np. Zapisy klientów), ale zwykle tak nie jest.
Tak więc, po prostu zmień wynik GetHashCode, gdy zmienią się dane obiektu i jeśli użycie obiektu wewnątrz skrótu przy użyciu list lub obiektów jest zamierzone (lub po prostu możliwe), uczyń obiekt albo niezmiennym, albo stwórz flagę tylko do odczytu do użycia dla czas życia zaszyfrowanej listy zawierającej obiekt.
(Nawiasem mówiąc: wszystko to nie jest specyficzne dla C # ani .NET - z natury wszystkich implementacji z hashtagami lub bardziej ogólnie każdej listy indeksowanej wynika, że dane identyfikacyjne obiektów nigdy nie powinny się zmieniać, gdy obiekt znajduje się na liście . Nieoczekiwane i nieprzewidywalne zachowanie nastąpi, jeśli ta reguła zostanie złamana. Gdzieś mogą istnieć implementacje list, które monitorują wszystkie elementy na liście i automatycznie reindeksują listę - ale wydajność tych z pewnością będzie w najlepszym przypadku makabryczna.)