Dlaczego ważne jest, aby przesłonić GetHashCode, gdy metoda Equals jest przesłonięta?

1444

Biorąc pod uwagę następującą klasę

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null) 
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

Przesłoniłem Equalsmetodę, ponieważ Fooreprezentują wiersz dla Footabeli s. Jaka jest preferowana metoda zastępowania GetHashCode?

Dlaczego warto to zmienić GetHashCode?

David Basarab
źródło
36
Ważne jest, aby zaimplementować zarówno equals, jak i gethashcode, ze względu na kolizje, w szczególności podczas korzystania ze słowników. jeśli dwa obiekty zwracają ten sam kod skrótu, są one wstawiane do słownika za pomocą łańcucha. Podczas uzyskiwania dostępu do przedmiotu używana jest metoda równa się.
DarthVader

Odpowiedzi:

1319

Tak, ważne jest, aby twój przedmiot był używany jako klucz w słowniku, HashSet<T>itp. - ponieważ jest on używany (w przypadku braku niestandardowego IEqualityComparer<T>) do grupowania przedmiotów w wiadra. Jeśli kod skrótu dla dwóch elementów nie pasuje, nigdy nie można ich uznać za równe ( Equals po prostu nigdy nie zostanie wywołany).

Metoda GetHashCode () powinna odzwierciedlać Equalslogikę; zasady są następujące:

  • jeśli dwie rzeczy są równe ( Equals(...) == true), muszą zwrócić tę samą wartość dlaGetHashCode()
  • jeśli GetHashCode()jest równy, nie musi być taki sam; jest to kolizja i Equalszostanie wezwana, aby sprawdzić, czy jest to prawdziwa równość, czy nie.

W tym przypadku wygląda na to, że „ return FooId;” jest odpowiednią GetHashCode()implementacją. Jeśli testujesz wiele właściwości, często łączy się je za pomocą kodu jak poniżej, aby zmniejszyć kolizje po przekątnej (tj. Tak, że new Foo(3,5)ma inny kod skrótu niż new Foo(5,3)):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

Och - dla wygody możesz również rozważyć dostarczenie ==i !=operatorów podczas nadpisywania Equalsi GetHashCode.


Wykazanie tego, co się dzieje, kiedy się to źle jest tutaj .

Marc Gravell
źródło
49
Czy mogę zapytać, czy pomnożycie te czynniki?
Leandro López
22
Właściwie prawdopodobnie mógłbym stracić jednego z nich; chodzi o to, aby zminimalizować liczbę kolizji - aby obiekt {1,0,0} miał inny skrót od {0,1,0} i {0,0,1} (jeśli rozumiesz, co mam na myśli) ),
Marc Gravell
13
Poprawiłem liczby, aby były wyraźniejsze (i dodałem ziarno). Niektóre kody używają różnych liczb - na przykład kompilator C # (dla typów anonimowych) używa zarodka 0x51ed270b i współczynnika -1521134295.
Marc Gravell
76
@Leandro López: Zazwyczaj czynniki są wybierane jako liczby pierwsze, ponieważ zmniejsza liczbę kolizji.
Andrei Rînea
29
„Och - dla wygody możesz również rozważyć podanie operatorów == i! = Podczas nadpisywania Equals i GethashCode.”: Microsoft odradza operator implementujący == dla obiektów, które nie są niezmienne - msdn.microsoft.com/en-us/library/ ms173147.aspx - "Nie jest dobrym pomysłem przesłonięcie operatora == w typach niezmiennych."
antiduh
137

W rzeczywistości bardzo trudno jest go GetHashCode()poprawnie wdrożyć, ponieważ oprócz reguł, o których już wspomniał Marc, kod skrótu nie powinien się zmieniać w trakcie życia obiektu. Dlatego pola używane do obliczania kodu skrótu muszą być niezmienne.

W końcu znalazłem rozwiązanie tego problemu, kiedy pracowałem z NHibernate. Moje podejście polega na obliczeniu kodu skrótu na podstawie identyfikatora obiektu. Identyfikator można ustawić tylko za pomocą konstruktora, więc jeśli chcesz zmienić identyfikator, co jest bardzo mało prawdopodobne, musisz utworzyć nowy obiekt, który ma nowy identyfikator, a zatem nowy kod skrótu. To podejście najlepiej działa z identyfikatorami GUID, ponieważ można dostarczyć konstruktora bez parametrów, który losowo generuje identyfikator.

Albic
źródło
20
@vanja. Wierzę, że ma to związek z: jeśli dodasz obiekt do słownika, a następnie zmienisz identyfikator obiektu, przy pobieraniu później będziesz używał innego skrótu, aby go odzyskać, więc nigdy nie otrzymasz go ze słownika.
ANeves
74
Dokumentacja Microsoft funkcji GetHashCode () nie stwierdza ani nie sugeruje, że skrót obiektu musi pozostać spójny przez cały okres jego istnienia. W rzeczywistości wyjaśnia konkretny dopuszczalny przypadek, w którym może nie : „Metoda GetHashCode dla obiektu musi konsekwentnie zwracać ten sam kod skrótu, o ile nie ma modyfikacji stanu obiektu, który określa wartość zwracaną przez metodę Equals obiektu . ”
PeterAllenWebb
37
„kod skrótu nie powinien się zmieniać w trakcie życia obiektu” - to nieprawda.
apokalipsa
7
Lepszym sposobem na stwierdzenie, że jest to „kod skrótu (ani ewaluacja równości) powinien ulec zmianie w okresie, w którym obiekt jest używany jako klucz do kolekcji”. Jeśli więc dodasz obiekt do słownika jako klucz, musisz upewnić się, że GetHashCode i Equals nie zmienią swoich danych wyjściowych dla danych wejściowych, dopóki nie usuniesz obiektu ze słownika.
Scott Chamberlain,
11
@ScottChamberlain Myślę, że nie zapomniałeś NIE w swoim komentarzu, powinno to być: „kod skrótu (ani ewaluacja równości) NIE powinien ulec zmianie w okresie, w którym obiekt jest używany jako klucz do kolekcji”. Dobrze?
Stan Prokop
57

Przesłaniając Equals, po prostu stwierdzasz, że jesteś tym, który wie lepiej, jak porównywać dwa wystąpienia danego typu, więc prawdopodobnie będziesz najlepszym kandydatem do zapewnienia najlepszego kodu skrótu.

Oto przykład, jak ReSharper pisze dla ciebie funkcję GetHashCode ():

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Jak widać, po prostu próbuje odgadnąć dobry kod skrótu na podstawie wszystkich pól w klasie, ale skoro znasz domenę lub zakresy wartości swojego obiektu, nadal możesz podać lepszy.

Pułapka
źródło
7
Czy to nie zawsze zwróci zero? Prawdopodobnie powinien zainicjować wynik na 1! Potrzebuje również kilku innych średników.
Sam Mackrill
16
Wiesz, co robi operator XOR (^)?
Stephen Drew,
1
Jak powiedziałem, to jest to, co R # pisze dla ciebie (przynajmniej tak zrobiło w 2008 roku), kiedy został o to poproszony. Oczywiście ten fragment ma zostać w jakiś sposób poprawiony przez programistę. Jeśli chodzi o brakujące średniki ... tak, wygląda na to, że je pominąłem, kiedy skopiowałem kod z zaznaczenia regionu w Visual Studio. Myślałem też, że ludzie to rozwiążą.
Pułapka
3
@SMMackrill Dodałem brakujące średniki.
Matthew Murdoch,
5
@SMMackrill Nie, nie zawsze zwróci 0. 0 ^ a = a, więc 0 ^ m_someVar1 = m_someVar1. Równie dobrze mógłby ustawić wartość początkową resultna m_someVar1.
Millie Smith,
41

Nie zapomnij sprawdzić parametru obj przed nullzastąpieniem Equals(). A także porównaj typ.

public override bool Equals(object obj)
{
    Foo fooItem = obj as Foo;

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

Powodem tego jest: Equalsmusi zwrócić false w porównaniu do null. Zobacz także http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

huha
źródło
6
To sprawdzenie typu zakończy się niepowodzeniem w sytuacji, gdy podklasa odwołuje się do metody równej nadklasy w ramach własnego porównania (tj. Base.Equals (obj)) - należy użyć zamiast niej
sweetfa
@sweetfa: Zależy to od sposobu wdrożenia metody Equals podklasy. Może również wywołać base.Equals ((BaseType) obj)), co działałoby poprawnie.
huha
2
Nie, nie będzie: msdn.microsoft.com/en-us/library/system.object.gettype.aspx . Poza tym implementacja metody nie powinna zakończyć się niepowodzeniem lub sukcesem, w zależności od tego, jak zostanie wywołana. Jeśli typ środowiska wykonawczego obiektu jest podklasą jakiejś klasy podstawowej, wówczas Equals () z klasy podstawowej powinien zwrócić wartość true, jeśli objrzeczywiście jest równa, thisbez względu na to, jak wywołano Equals () z klasy podstawowej.
Jowisz
2
Przesunięcie fooItemdo góry, a następnie sprawdzenie jej pod kątem wartości NULL będzie działać lepiej w przypadku wartości NULL lub niewłaściwego typu.
IllidanS4 chce powrotu Moniki
1
@ 40Alpha Cóż, tak, to obj as Foobyłoby nieprawidłowe.
IllidanS4 chce powrotu Moniki
35

Co powiesz na:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

Zakładając, że wydajność nie stanowi problemu :)

Ludmil Tinkov
źródło
1
erm - ale
zwracasz
32
Nie, wywołuje funkcję GetHashCode () z obiektu String, która zwraca wartość int.
Richard Clayton,
3
Nie oczekuję, że będzie to tak szybkie, jak bym chciał, nie tylko dla boksu związanego z typami wartości, ale także dla wydajności string.Format. Innym naukowym, którego widziałem, jest new { prop1, prop2, prop3 }.GetHashCode(). Nie mogę komentować, który z nich byłby wolniejszy. Nie nadużywaj narzędzi.
nawfal
16
To zwróci prawdę dla { prop1="_X", prop2="Y", prop3="Z" }i{ prop1="", prop2="X_Y", prop3="Z_" } . Prawdopodobnie tego nie chcesz.
voetsjoeba
2
Tak, zawsze możesz zastąpić symbol podkreślenia czymś nie tak powszechnym (np. •, ▲, ►, ◄, ☺, ☻) i mam nadzieję, że użytkownicy nie będą używać tych symboli ... :)
Ludmil Tinkov
13

Mamy dwa problemy do rozwiązania.

  1. Nie można podać sensownego, GetHashCode()jeśli można zmienić dowolne pole w obiekcie. Często też obiekt NIGDY nie będzie używany w kolekcji, od której zależy GetHashCode(). Tak więc koszt wdrożenia GetHashCode()często nie jest tego wart, lub nie jest to możliwe.

  2. Jeśli ktoś umieści Twój obiekt w kolekcji, która wywołuje, GetHashCode()a Ty przesłoniłeś, Equals()nie GetHashCode()zachowując się również we właściwy sposób, osoba ta może spędzić dni na śledzeniu problemu.

Dlatego domyślnie tak.

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        if (fooItem == null)
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}
Ian Ringrose
źródło
5
Zgłoszenie wyjątku z GetHashCode stanowi naruszenie umowy Object. Nie ma trudności w zdefiniowaniu GetHashCodefunkcji, tak że dowolne dwa równe obiekty zwracają ten sam kod skrótu; return 24601;i return 8675309;obie byłyby poprawnymi implementacjami GetHashCode. Wydajność Dictionarybędzie przyzwoita tylko wtedy, gdy liczba przedmiotów będzie niewielka, a pogorszy się, jeśli liczba przedmiotów się zwiększy, ale i tak będzie działać poprawnie.
supercat,
2
@ superupat, Nie jest możliwe zaimplementowanie GetHashCode w rozsądny sposób, jeśli pola identyfikacyjne w obiekcie mogą ulec zmianie, ponieważ kod skrótu nigdy się nie zmienia. Wykonanie tego, co powiesz, może spowodować, że ktoś będzie musiał spędzić wiele dni na śledzeniu problemu z wydajnością, a następnie wiele tygodni na przeprojektowaniu dużego systemu w celu wyeliminowania korzystania ze słowników.
Ian Ringrose,
2
Robiłem coś takiego dla wszystkich zdefiniowanych przeze mnie klas, które wymagały Equals (), i gdzie byłem całkowicie pewien, że nigdy nie użyję tego obiektu jako klucza w kolekcji. Pewnego dnia program, w którym użyłem takiego obiektu jako danych wejściowych do kontrolki DevExpress XtraGrid, się zawiesił. Okazuje się, że XtraGrid za moimi plecami tworzył tabelę HashTable lub coś w oparciu o moje obiekty. Wdałem się w drobny spór z osobami wspierającymi DevExpress na ten temat. Powiedziałem, że nie było mądre, że oparli funkcjonalność i niezawodność swojego komponentu na wdrożeniu nieznanej metody nieznanego klienta.
RenniePet
Ludzie DevExpress byli raczej powściągliwi, mówiąc w zasadzie, że muszę być idiotą, aby zgłosić wyjątek w metodzie GetHashCode (). Nadal uważam, że powinni znaleźć alternatywną metodę robienia tego, co robią - przypominam Marcowi Gravellowi inny wątek opisujący, w jaki sposób buduje słownik dowolnych obiektów, nie będąc zależnym od GetHashCode () - nie pamiętam, jak to zrobił chociaż.
RenniePet
4
@RenniePet, musi lepiej mieć sympatię z powodu zgłoszenia wyjątku, a następnie mieć bardzo trudny do znalezienia błąd z powodu nieprawidłowej implementacji.
Ian Ringrose
12

Wynika to z faktu, że struktura wymaga, aby dwa identyczne obiekty musiały mieć ten sam kod skrótu. Jeśli przesłonisz metodę równości, aby wykonać specjalne porównanie dwóch obiektów, a te dwa obiekty zostaną uznane przez metodę za identyczne, wówczas kod skrótu tych dwóch obiektów również musi być taki sam. (Słowniki i tabele skrótów opierają się na tej zasadzie).

kemiller2002
źródło
11

Aby dodać powyższe odpowiedzi:

Jeśli nie zastąpisz opcji Równa się, domyślnym zachowaniem jest porównywanie odniesień do obiektów. To samo dotyczy hashcode - domyślna implementacja zwykle opiera się na adresie pamięci odniesienia. Ponieważ zastąpiłeś Equals, oznacza to, że poprawnym zachowaniem jest porównanie wszystkiego, co zaimplementowałeś w Equals, a nie referencji, więc powinieneś zrobić to samo dla kodu skrótu.

Klienci twojej klasy oczekują, że kod skrótu będzie miał logikę podobną do metody równości, na przykład metody linq, które używają IEqualityComparer najpierw porównają kody skrótu i ​​tylko jeśli są równe, porównają metodę Equals (), która może być droższa aby uruchomić, jeśli nie zaimplementujemy hashcode, równy obiekt prawdopodobnie będzie miał różne hashcode (ponieważ mają inny adres pamięci) i zostanie błędnie określony jako nierówny (Equals () nawet nie trafi).

Ponadto, z wyjątkiem problemu polegającego na tym, że możesz nie być w stanie znaleźć swojego obiektu, jeśli używałeś go w słowniku (ponieważ został on wstawiony przez jeden kod skrótu, a gdy go szukasz, domyślny kod skrótu prawdopodobnie będzie inny i znowu będzie równy () nawet nie zostanie wywołany, jak wyjaśnia Marc Gravell w swojej odpowiedzi, wprowadzasz również naruszenie słownika lub koncepcji skrótu, które nie powinny dopuszczać identycznych kluczy - już zadeklarowałeś, że te obiekty są zasadniczo takie same, gdy przesłonisz Equals, więc nie nie chcę obu z nich jako różnych kluczy w strukturze danych, które mają mieć unikalny klucz, ale ponieważ mają one inny kod skrótu, „ten sam” klucz zostanie wstawiony jako inny.

BornToCode
źródło
8

Kod skrótu jest używany w kolekcjach opartych na skrócie, takich jak Dictionary, Hashtable, HashSet itp. Celem tego kodu jest bardzo szybkie wstępne sortowanie określonego obiektu przez umieszczenie go w określonej grupie (segmencie). To wstępne sortowanie ogromnie pomaga w znalezieniu tego obiektu, gdy trzeba go odzyskać z kolekcji mieszania, ponieważ kod musi wyszukiwać obiekt w jednym wiadrze zamiast we wszystkich obiektach w nim zawartych. Im lepsza dystrybucja kodów skrótu (lepsza unikalność), tym szybsze pobieranie. W idealnej sytuacji, w której każdy obiekt ma unikalny kod skrótu, znalezienie go jest operacją O (1). W większości przypadków zbliża się do O (1).

Maciej
źródło
7

To niekoniecznie ważne; zależy to od wielkości twoich kolekcji i wymagań dotyczących wydajności oraz od tego, czy Twoja klasa będzie używana w bibliotece, w której możesz nie znać wymagań dotyczących wydajności. Często wiem, że moje rozmiary kolekcji nie są bardzo duże, a mój czas jest cenniejszy niż kilka mikrosekund wydajności osiągniętej dzięki stworzeniu idealnego kodu skrótu; więc (aby pozbyć się irytującego ostrzeżenia kompilatora) po prostu używam:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(Oczywiście, mógłbym użyć #pragmy również do wyłączenia ostrzeżenia, ale wolę w ten sposób.)

Po pojawieniu się w położeniu, które nie potrzebują wydajność niż wszystkich zagadnień wymienionych przez innych tutaj zastosować, oczywiście. Najważniejsze - w przeciwnym razie przy pobieraniu elementów z zestawu skrótów lub słownika wystąpią błędne wyniki: kod skrótu nie może różnić się w zależności od czasu życia obiektu (a dokładniej w czasie, gdy potrzebny jest kod skrótu, na przykład podczas bycia klucz w słowniku): na przykład poniższe informacje są niepoprawne, ponieważ wartość jest publiczna i dlatego można ją zmienić zewnętrznie na klasę w czasie istnienia instancji, więc nie wolno jej używać jako podstawy kodu skrótu:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

Z drugiej strony, jeśli nie można zmienić wartości, można użyć:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }
ILoveFortran
źródło
3
Doceniony. To jest po prostu źle. Nawet Microsoft stwierdza w MSDN ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ), że wartość GetHashCode MUSI się zmieniać, gdy stan obiektu zmienia się w sposób, który może wpływać na wartość zwracaną wywołania na Equals (), a nawet w swoich przykładach pokazuje implementacje GetHashCode, które w pełni zależą od publicznie zmienianych wartości.
Sebastian PR Gingter
Sebastian, nie zgadzam się: jeśli dodasz obiekt do kolekcji używającej kodów skrótu, zostanie on umieszczony w koszu zależnym od kodu skrótu. Jeśli zmienisz teraz kod skrótu, nie znajdziesz ponownie obiektu w kolekcji, ponieważ zostanie przeszukany niewłaściwy pojemnik. Jest to w rzeczywistości coś, co wydarzyło się w naszym kodzie i dlatego uważam za konieczne wskazanie tego.
ILoveFortran
2
Sebastian, Ponadto nie widzę w łączu instrukcji ( msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx ), którą GetHashCode () musi zmienić. Wręcz przeciwnie - NIE może się zmieniać, dopóki Equals zwraca tę samą wartość dla tego samego argumentu: „Metoda GetHashCode dla obiektu musi konsekwentnie zwracać ten sam kod skrótu, o ile nie ma zmian w stanie obiektu, który określa wartość zwracaną metody obiektu równa się. ”To stwierdzenie nie oznacza przeciwnie, że musi się zmienić, jeśli zmieni się wartość zwracana dla równości.
ILoveFortran
2
@ Joao, mylisz stronę klienta / konsumenta umowy z producentem / realizatorem. Mówię o odpowiedzialności implementatora, który zastępuje GetHashCode (). Mówisz o konsumentu, tym, który używa wartości.
ILoveFortran
1
Całkowite nieporozumienie ... :) Prawda jest taka, że ​​kod skrótu musi się zmieniać, gdy zmienia się stan obiektu, chyba że stan nie ma znaczenia dla tożsamości obiektu. Ponadto nigdy nie należy używać obiektu MUTABLE jako klucza w swoich kolekcjach. W tym celu należy używać obiektów tylko do odczytu. GetHashCode, Equals ... i niektóre inne metody, których nazw nie pamiętam w tej chwili, NIGDY nie powinny rzucać.
darlove
0

Zawsze powinieneś gwarantować, że jeśli dwa obiekty są równe, zgodnie z definicją Equals (), powinny zwrócić ten sam kod skrótu. Jak twierdzą niektóre inne komentarze, teoretycznie nie jest to obowiązkowe, jeśli obiekt nigdy nie będzie używany w kontenerze opartym na haszowaniu, takim jak HashSet lub Dictionary. Radziłbym ci jednak zawsze przestrzegać tej zasady. Powodem jest po prostu to, że zbyt łatwo jest komuś zmienić kolekcję z jednego typu na inny z dobrym zamiarem faktycznej poprawy wydajności lub po prostu lepszego przekazania semantyki kodu.

Załóżmy na przykład, że przechowujemy niektóre obiekty na liście. Jakiś czas później ktoś faktycznie zdaje sobie sprawę, że HashSet jest znacznie lepszą alternatywą, na przykład ze względu na lepszą charakterystykę wyszukiwania. To wtedy możemy wpaść w kłopoty. Lista wewnętrznie użyje domyślnego modułu porównującego równość dla typu, co w twoim przypadku oznacza Equals, podczas gdy HashSet korzysta z GetHashCode (). Jeśli oba zachowują się inaczej, twój program również. I pamiętaj, że takie problemy nie są najłatwiejsze do rozwiązania.

Podsumowałem to zachowanie przy pomocy innych pułapek GetHashCode () w poście na blogu, gdzie można znaleźć dalsze przykłady i wyjaśnienia.

Vasil Kosturski
źródło
0

Jako .NET 4.7preferowaną metodę zastępowania GetHashCode()pokazano poniżej. Jeśli celujesz w starsze wersje .NET, dołącz pakiet nuget System.ValueTuple .

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

Pod względem wydajności ta metoda przewyższy większość implementacji złożonego kodu mieszającego. ValueTuple jeststruct tak, że nie będzie żadnych śmieci, a algorytm bazowy jest tak szybko, jak to tylko możliwe.

l33t
źródło
-1

Rozumiem, że pierwotna funkcja GetHashCode () zwraca adres pamięci obiektu, więc konieczne jest zastąpienie go, jeśli chcesz porównać dwa różne obiekty.

ZMIENIONO: To było niepoprawne, oryginalna metoda GetHashCode () nie może zapewnić równości 2 wartości. Jednak równe obiekty zwracają ten sam kod skrótu.

użytkownik2855602
źródło
-6

Poniżej użycie refleksji wydaje mi się lepszą opcją biorąc pod uwagę właściwości publiczne, ponieważ dzięki temu nie musisz się martwić o dodawanie / usuwanie właściwości (choć nie tak powszechny scenariusz). Okazało się, że również działa lepiej (w porównaniu do czasu przy użyciu stopera Diagonistics).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }
Guanxi
źródło
12
Implementacja GetHashCode () ma być bardzo lekka. Nie jestem pewien, czy użycie StopWatch jest zauważalne przy tysiącach wywołań, ale z pewnością dotyczy milionów (pomyśl o zapełnieniu słownika z listy).
bohdan_trotsenko