GetHashCode() - czym dokładnie jest?

GetHashCode() - czym dokładnie jest?
VarrComodoo
  • Rejestracja:około 14 lat
  • Ostatnio:10 dni
  • Lokalizacja:Bk
  • Postów:480
0

Nie rozumiem czym dokładnie jest wynik zwracany przez GetHashCode(), mogły ktoś jeszcze swoimi słowami podpowiedzieć czym to jest?

Musiałem ponadpisywać metody porównania i potworzyć przeciążenia operatorów ==,!=,+,- dla swojego typu, o ile bez problemu rozumiem na czym mają polegać metody Equal() i przeciążenia operatorów, kompletnie nie rozumiem co się dzieje w GetHashCode() - tą implementacje zerżnąłem z książki.

Kopiuj
class CzasScala
{
///.....
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            if (this.GetType() != obj.GetType())
                return false;
            return Equals((CzasScala)obj);
        }
        private bool Equals(CzasScala obj)
        {
            if (ReferenceEquals(this, obj))
                return true;
            if (this.GetHashCode() != obj.GetHashCode())
                return false;

            return (this.SumaSekund == obj.sumaSekund) &&
                (this.dokladnosc_CzasDec_PoPrzecinku == obj.dokladnosc_CzasDec_PoPrzecinku);
        }
        public override int GetHashCode()
        {
            int hashCode = dokladnosc_CzasDec_PoPrzecinku.GetHashCode();
            if (dokladnosc_CzasDec_PoPrzecinku.GetHashCode() != SumaSekund.GetHashCode())
            {
                hashCode ^= SumaSekund.GetHashCode();
            }

            return hashCode;
        }
}

alternatywa po odwróceniu wlasciwosci /dokladnosc_czasDec_PoPrzecinku - używam do zaprezentowania czas w formie wartości dziesiętnej, to pole jest prywatne i ma zawsze wartosc 4:

Kopiuj
        public override int GetHashCode()
        {
            int hashCode = SumaSekund.GetHashCode();
            if (dokladnosc_CzasDec_PoPrzecinku.GetHashCode() != SumaSekund.GetHashCode())
            {
                hashCode ^= dokladnosc_CzasDec_PoPrzecinku.GetHashCode();
            }
            return hashCode;
        }
}

Najbardziej nie rozumiem wymogu dla GetHashCode() aby

"wartości GetHashCode() w czasie życia danego obiektu powinny być takie same - także wtedy gdy dane w obiekcie się zmieniają - "C# kompletny przewodnik dla praktyków" Mark Michaelis str.378"

ale po zerżnięciu implementacji to wymaganie nie jest spełnione i widać to w wynikach z konsoli w ostatniej linii, rozumiem dlaczego bo przecież SumaSekund się zmieniła z 67 na 65 ale nie rozumiem jak to zrobić aby po zmianie SumaSekund wartość HashCode się nie zmieniła.

Kopiuj
            CzasScala czas1 = new CzasScala(67);
            CzasScala czas2 = new CzasScala(2);
            CzasScala czas3 = new CzasScala(65);
            CzasScala czas4 = new CzasScala(65);
            CzasScala czas5 = czas3;
            CzasScala czas6 = new CzasScala(4);

            Console.WriteLine($"Czas: {czas1.SumaSekund}, HashCode czas1: {czas1.GetHashCode()}");
            Console.WriteLine($"Czas: {czas2.SumaSekund}, HashCode czas2: {czas2.GetHashCode()}");
            Console.WriteLine($"Czas: {(czas1 - czas3).SumaSekund}, HashCode czas1-czas3: {(czas1 - czas3).GetHashCode()}");
            Console.WriteLine($"Czas: {czas3.SumaSekund}, HashCode czas3: {czas3.GetHashCode()}");
            Console.WriteLine($"Czas: {czas4.SumaSekund}, HashCode czas4: {czas4.GetHashCode()}");
            Console.WriteLine($"Czas: {czas5.SumaSekund}, HashCode czas5: {czas5.GetHashCode()}");
            Console.WriteLine($"Czas: {czas6.SumaSekund}, HashCode czas6: {czas6.GetHashCode()}");
            Console.WriteLine($"Czas: {(czas2+czas2).SumaSekund}, HashCode (czas2+czas2): {(czas2 + czas2).GetHashCode()}");
            czas1 = (czas1 - czas2);
            Console.WriteLine($"Czas: {czas1.SumaSekund}, HashCode czas1 po zmianie wartosci o -czas2 czy hashcode bedzie rowny czas3?: {czas1.GetHashCode()}");

            Console.ReadLine();

Zdjęcie strony 378
IMG_20210413_090917.jpg


Sterczące kolce Pondijusa, ostre grzebienie Daktyloskopei, Trygla i latający Wieprzoryb są niczym wobec Bestii która nas gnębi...
edytowany 3x, ostatnio: VarrComodoo
koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
3

Cyba warto rzucić okiem na podstawy teoretyczne:
https://pl.wikipedia.org/wiki/Funkcja_skr%C3%B3tu
-- jak to Ci nie rozjaśni, to pytaj dalej (ale po przeczytaniu artykułu z Wikipedii! :))

VarrComodoo
Spoko dzieki, troch potrwa zanim to przetrawie i ogarne
lion137
  • Rejestracja:około 8 lat
  • Ostatnio:5 minut
  • Postów:4924
2

koszalek-opalek
  • Rejestracja:około 9 lat
  • Ostatnio:ponad 2 lata
1

A na marginesie: nie wydaje Wam się, że Wymagane nr 1 jest w sprzeczności z Wymaganym nr 2 (ze zdjęcia, które @Varran opublikował) -- jeśli obiekt jest mutowalny? Jak z tego wybrnąć?

PS. Poza nieużywaniem obiektów mutowalnych (popieram)... :)

edytowany 1x, ostatnio: koszalek-opalek
Zobacz pozostałe 4 komentarze
VarrComodoo
Z tego co podlinkowal @lion137 to zasada przeczy temu co w ksiazce
VarrComodoo
The GetHashCode() method for an object must consistently return the same hash code as long as there is no modification to the object state that determines the return value of the object's System.Object.Equals method. Note that this is true only for the current execution of an application, and that a different hash code can be returned if the application is run again.
somekind
Jak dla mnie, to dla value objectu (czyli obiektu, którego tożsamość jest definiowana jego zawartością) hash się musi zmienić, dla encji (czyli jakiegokolwiek obiektu posiadającego nadane w jakikolwiek sposób ID) nie. To jest w ogóle ortogonalne do mutowalności. Wszelkiego typu obiekty-procesy, czyli serwisy, handlery, repozytoria, itd. w ogóle hasha nie potrzebują.
VarrComodoo
@somekind: To ze do obiektow nadzorujacych logika nie potrzeba hash'a to rozumiem, bo i nie ma co porownywac dwoch serwisow czy repozytoriow. Z Twojej wypowiedzi zrozumialem ze obiekty z jakas logika np. Jakies agregaty w logice powinny miec zmieniajacego sie hash'a wraz ze zmiana ich wartosci bo na nich zamierzam wykonywac operacje porownan i operacje arytmetyczne. A czyste encje z bazy sa i tak czyste bez zadnych metod bo sluza tylko do potworzenia obiektow w logice?
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:4 dni
  • Lokalizacja:Wrocław
4

@Varran: no nie o to mi chodziło, więc spróbuję dokładniej w poście, bo w komentarzu i tak się nie zmieści.

Encja to coś, co ma swoją tożsamość, np. człowiek, faktura - człowieka identyfikujesz po peselu, fakturę po numerze, w ogólności dowolną encję możesz po jakimś jej naturalnym identyfikatorze odróżnić. Encja w ogólności nie ma żadnego związku z bazą danych.
Value object to coś, co nie ma własnej tożsamości, więc żeby porównać dwa takie obiekty trzeba porównać ich zawartość. Value objecty to np. punkt na płaszczyźnie (jeśli współrzędne są równe, to to jest ten sam punkt), cena (jeśli kwota i waluta się zgadzają to to jest taka sama cena), itp.

Przy tym założeniu, to w przypadku encji GetHashCode to będzie GetHashCode jej identyfikatora. W przypadku value objectów GetHashCode musi używać wszystkich pól, aby mogło się zmienić po zmianie wartości któregokolwiek pola (czyli wtedy, gdy zmieni się też wynik Equals).

Nie wiem co dokładnie Twój kod ma robić, ale na pewno możesz swoje obiekty przypisać do jednej z tych dwóch grup i odpowiednio zaimplementować GetHashCode. I wtedy będzie to działanie zgodne z tym, co jest opisane w książce.

A czyste encje z bazy sa i tak czyste bez zadnych metod bo sluza tylko do potworzenia obiektow w logice?

Ja nie nazywam takich obiektów encjami tylko persistence modelami, właśnie żeby uniknąć zamieszania z nazewnictwem. Te persistence modele zazwyczaj są encjami (bo maja swoją tożsamość definiowaną przez jakiś unikalny identyfikator, w najgorszym razie nawet sztuczny klucz z bazy). Ale w ogóle nie ma znaczenia, czy masz takie anemiczne modele, czy mapujesz tabele do jakichś pełnoprawnych "bogatych" encji, liczy się to jak je identyfikujesz.

Zobacz pozostały 1 komentarz
somekind
Sam jesteś niepraktyczny koncept.
WeiXiao
@somekind: no to kiedy się przydaje? bo zazwyczaj więcej problemów generuje
somekind
Jakich problemów?
WeiXiao
że założenia klucza naturalnego się sypnęły
somekind
No, ale konkretnie w jakim kontekście mają się sypnąć? Np. numer faktury musi być unikalny i tyle, takie jest prawo. I ja o identyfikatorze cały czas piszę, nie o kluczu. Jeśli chodzi Ci o jakieś problemy z bazą danych, to przecież co do zasady nie używa się identyfikatora naturalnego jako klucza głównego, i ja niczego takiego nie sugeruję.
obscurity
  • Rejestracja:około 6 lat
  • Ostatnio:około 3 godziny
5

Wydaje mi się że nikt nie odpowiedział na zadane pytanie ani z czego wynikają te wymagania.
GetHashCode ma po prostu zwrócić liczbę która wstępnie zaklasyfikuje obiekt. Używane to jest przykładowo w słownikach, gdzie dodane dane są podzielone na "kubełki" (buckets). Dla uproszczenia powiedzmy że obiekt trafia do jednego z 10 kubełków na podstawie ostatniej cyfry. Gdy próbujesz potem sprawdzić czy wartość jest w słowniku, najpierw bierzemy GetHashCode obiektu który sprawdzasz i przeglądamy zawartość tylko jednego z tych kubełków porównując je po kolei (używając Equals).

Jak pewnie się domyślasz - pierwsze dwa wymagania wynikają z tego że musisz szukać obiektu w tym samym kubełku do którego je wrzuciłeś.
Mógłbyś zawsze zwracać stałą liczbę, powiedzmy "0" - wtedy wszystkie obiekty trafią do tego samego kubła i na pewno je odnajdziesz z powrotem - ale potem przy sprawdzaniu będziesz musiał przejrzeć wszystkie obiekty wrzucone do jednego kubełka zamiast tylko 1/10 co będzie miało wpływ na wydajność (ostatnie wymaganie).

Tak naprawdę zasady są luźniejsze - dotyczą tylko elementów których używasz właśnie w hashowanych kolekcjach jako klucz. Nie powinno się tam używać obiektów mutowalnych (zazwyczaj kluczem i tak jest typ prosty jak liczba / GUID czy string) dlatego też możesz spokojnie generować hashcode na podstawie danych. Hashcode może też się zmieniać dowoli dopóki obiekt nie zostanie do takiej kolekcji dodany, czasami stosuje się "zamrażanie" obiektów https://github.com/fodyarchived/Freezable - do pewnego momentu obiekt jest mutowalny, w pewnym momencie następuje jego zamrożenie i dalsze zmiany nie są możliwe - wtedy może nastąpić wyliczenie hashcode'u.
W praktyce praktycznie nie widziałem innej implementacji niż wyliczania hashcode'u na podstawie danych. Gdy zrozumiesz jak to wszystko działa będzie Ci łatwiej podjąć decyzję.

Co ciekawe - z rozmów kwalifikacyjnych wiem że ponad 90% programistów w ogóle nie rozumie czym jest HashCode. Na pytanie "co się stanie gdy spróbujemy dodać do Dictionary drugi obiekt o tym samym hashcodzie" w rozmowie na stanowisko seniorskie(!) znaczna większość odpowiada że zostanie rzucony wyjątek lub że element nie zostanie dodany do kolekcji.


"A car won't take your job, another horse driving a car will." - Horse influencer, 1910
edytowany 3x, ostatnio: obscurity
WeiXiao
  • Rejestracja:około 9 lat
  • Ostatnio:około 4 godziny
  • Postów:5132
0

zamiast klepać z ręki hashcody, to użyj VS i on ci wygeneruje ładny GetHashCode, a w dodatku zastosuje HashCode.Combine

screenshot-20210413185313.png

screenshot-20210413185343.png+

@obscurity

Co ciekawe - z rozmów kwalifikacyjnych wiem że ponad 90% programistów w ogóle nie rozumie czym jest HashCode. Na pytanie "co się stanie gdy spróbujemy dodać do Dictionary drugi obiekt o tym samym hashcodzie" w rozmowie na stanowisko seniorskie(!) znaczna większość odpowiada że zostanie rzucony wyjątek lub że element nie zostanie dodany do kolekcji.

jesteś tego pewien co tutaj napisałeś? ;)

bo wiesz, głupio byłoby gdybyś się tu chichrał z señor, a mimo to pisał coś, co nie odzwierciedla rzeczywistości :P

edytowany 3x, ostatnio: WeiXiao
obscurity
co masz na myśli? Mam rozumieć że należysz do tych którzy myślą że dwa obiekty nie mogą mieć tego samego hashcode'u?
WeiXiao
@obscurity: dobra nvm, źle przetestowałem, a chciałem być złośliwy :P
WeiXiao
zrobiłem var a = new A(); dic.Add(a, 5); dic.Add(a, 5); z GetHashCode return 5; i wywaliło mi wyjątek, ale przy nowym obiekcie już nie, więc jednak nie leci.
obscurity
aha czyli jednak. Gdyby ten sam hashcode powodował rzucanie wyjątków to byłoby to niezłe piekło programisty a metoda Equals byłaby zbędna
ZK
  • Rejestracja:prawie 7 lat
  • Ostatnio:5 miesięcy
  • Postów:273
1

@Varran: https://docs.microsoft.com/en-us/dotnet/api/system.object.gethashcode?view=net-5.0
W dokumentacji Microsoftu napisane jest, że tych nieszczęsnych kodów skrótów powinno się używać wyłącznie w kolekcjach takich jak Dictionary<TKey,TValue> ,Hashtable lub pochodnych klasy DictionaryBase

edytowany 1x, ostatnio: Zimny Krawiec

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.