metoda T.Equals(T objToCompare) porównuje 2 instancje obiektów, zwracając boolowską wartość, czy są sobie równe czy nie. Przeciąża się to we własnych klasach, aby umożliwić rozmaitym IComparerom porównywanie naszych obiektów. To wiedzą wszyscy.
Metoda ta należy do typu object, więc każda instancja obiektu ma tą metodę.
Nieco większą zagadką jest metoda GetHashCode.
Co prawda visual studio rzuca nam warningiem, jeżeli przeciążymy metodę Equals, nie przeciążąjąc metody GetHashCode. Nie wiem jak inni, ja się w ten warning nie zagłębiałem. Po nazwie metody wywnioskowałem że zwraca ona hash do tablic hashujących czyli np Dictionary. W końcu porównywanie hashy jest zawsze szybsze niż porównywanie całych obiektów, to to nawet na studiach było na 2 roku na AiSD.
A jakoś nie zdarzyło mi się jeszcze używać w Dictionary własnych obiektów które musiałbym porównywać między sobą operatorem Equals() lub ==, więc jakoś sobie radziłem z brakiem tej wiedzy.
Tymczasem dziś przypadkowo znalazłem:
"Dlaczego przeciążąnie GetHashCode() jest ważne? Jeżeli Hash zwrócony przez GetHashCode dla dwóch różnych obiektów nie będzie sobie równy, te obiekty nigdy nie zostaną uznane za równe sobie, i metoda Equals() nie zostanie dla nich uruchomiona nigdy. Powinieneś w pierwszej kolejności przeciążyć GetHashCode() i zwracać ten sam hash dla dwóch równych sobie obiektów".
No proszę.. .NET już domyślnie wykorzystuje ten fajny algorytm o którym się uczyliśmy tak dawno, porównując najpierw hashe obiektów, a dopiero wtedy gdy się zgadzają - uruchamiając funkcję compare (bo przecież może być kolizja hashy i nie można na samych hashach w 100% polegać).
Teraz nawet sobie przypominam że dziwiłem się czego Equals() nie działa w jakims swoim małym programiku.. ale zamiast wgłębiać się w przyczyny, średnio był czas na to przy "nieciekawym" małym programiku, po prostu napisałem porównanie w metodzie "na sztywno" i też działało. I zapomniałem o sprawie.
Dobrze że na to trafiłem, człowiek uczy się całe życie, tym bardziej tak rozbudowanego środowiska jakim jest .NET. Czasem robiąc "ciekawsze" rzeczy, można zapomnieć o podstawach :)
Skoro już jesteśmy przy liczeniu hashy, podzielę się z wami tym co wyniosłem z zabawnego wykładu AiSD który w całości był dyktowany z pożółkłej już ze starości kartki :) (Niech żyje S....! Niech żyje P...!)
Metoda GetHashCode() powinna być raczej prosta i szybka w wykonywaniu niż bardzo dokładna (w sensie rzadko generująca kolizję). Jej celem jest odsianie wstępne większości obiektów, które na pewno nie będą sobie równe z racji róźnych hashy, kolizje i tak zostaną zweryfikowane metodą Equals. Chodzi tu o szybkość porównywania, więc lepiej jeżeli metoda GetHashCode generuje częstsze kolizje (np w 10% przypadków) ale będzie 10x szybsza niż jeżeli wygeneruje kolizje w przypadku 1%, ale będzie 10x wolniejsza. Jeżeli już uruchomi się metoda Equals, w której np. sprawdzimy wszystkie pola klasy metodami Equals, to dla nich też, zanim zostanie uruchomione Equals, zostanie najpierw uruchomione ich GetHashCode, więc ta metoda Equals wcale nie musi być dużo wolniejsza od samego GetHashCode. Może kiedyś wróce do zagadnienia (jak nie będe miał na głowie 7 sprawozdań na jutro), i machnę mały teścik z różnymi strategiami tego, z odpowiedzią która jest najwydajniejsza.
Choć i tak w 90% przypadków wydajność nie będzie miała żadnego znaczenia - a jeśli już bierzemy sie za przetwarzanie dużych zbiorów danych, dlaczego robić to w kodzie a nie użyć do tego np. bazy danych SQL?
Częste kolizje w GetHashCode() mogą jedynie popsuc wydajność w hashowanych kolekcjach (HashMap, HashSet), jeżeli twoja klasa nie bedzie jakoś intensywnie używana w tych kolekcjach, to lepiej zrobić szybszą GetHashCode().
Wg mnie najprostszą metodą na GetHashCode i zapewne wcale nie najgorszą będzie po prostu dodanie do siebie wartości zwróconej przez metodę GetHashCode dla wszystkich pól naszej klasy, i pomnożenie każdej z nich przez inną liczbę pierwszą. To wygeneruje dużo kolizji, ale będzie bardzo szybkie.
Innym sposobem jest hashGenerator wbudowany we framework. Podobno ten generator jest bardzo dobrej jakości (mało kolizji). I nie dublujemy kodu.
return new { A = PropA, B = PropB, C = PropC, D = PropD }.GetHashCode();
Podstawiamy do anonimowego obiektu pola naszej klasy, i uruchamiamy metode GetHashCode.
jednak obawiam się że tworzenie za kazdym razem nowego obiektu może nie być wcale takie szybkie jakbyśmy tego oczekiwali. Jednak dla miejsc niekrytycznych dla wydajności kodu, taka implementacja będzie w pełni wystarczająca.
Należy również pamiętać że wiele typów w .NET (również tych wbudowanych) nie gwarantuje że hashe będą stałe pomiędzy kolejnymi uruchomieniami aplikacji. Identyczny obiekt wczytany w 2 różnych instancjach aplikacji prawdopodobnie będzie miał różny hash, więc do porównywania obiektów pomiędzy instancjami trzeba zrobić jakieś dodatkowe ID, na pewno nie powinno się używać do tego hashy z tej metody.