GetHashCode() - czym dokładnie jest?

GetHashCode() - czym dokładnie jest?
VarrComodoo
  • Rejestracja:około 14 lat
  • Ostatnio:6 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:2 minuty
  • Postów:4896
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:2 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:minuta
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 2 godziny
  • Postów:5110
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
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)