Metoda Rozszerzenia GetHashCode

Po przeczytaniu wszystkich pytań i odpowiedzi na StackOverflow dotyczących nadpisywania GetHashCode() napisałem następującą metodę rozszerzenia dla łatwego i wygodnego nadpisywania GetHashCode():

public static class ObjectExtensions
{
    private const int _seedPrimeNumber = 691;
    private const int _fieldPrimeNumber = 397;
    public static int GetHashCodeFromFields(this object obj, params object[] fields) {
        unchecked { //unchecked to prevent throwing overflow exception
            int hashCode = _seedPrimeNumber;
            for (int i = 0; i < fields.Length; i++)
                if (fields[i] != null)
                    hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();
            return hashCode;
        }
    }
}

(w zasadzie tylko zrefakturowałem kod, który ktoś tam zamieścił, bo bardzo podoba mi się, że można go używać ogólnie)

Którego używam Tak:

    public override int GetHashCode() {
        return this.GetHashCodeFromFields(field1, field2, field3);
    }
Widzisz jakieś problemy z tym kodem?
Author: Jonathan Rupp, 2009-04-18

9 answers

Wygląda to na solidny sposób.

Moją jedyną sugestią jest to, że jeśli naprawdę martwisz się o wydajność, możesz dodać ogólne wersje dla kilku typowych przypadków(np. prawdopodobnie 1-4 args). W ten sposób, dla tych obiektów (które są najprawdopodobniej małymi obiektami kompozytowymi w stylu klucza), nie będziesz mieć narzutu budowania tablicy do przekazania metodzie, pętli, jakiegokolwiek boksu wartości ogólnych itp. Składnia wywołania będzie dokładnie taka sama, ale uruchomisz nieco bardziej zoptymalizowany kod do tego przypadku. Oczywiście, przeprowadziłbym kilka testów perf, zanim zdecydujesz, czy jest to warte wymiany konserwacyjnej.

Coś takiego:

public static int GetHashCodeFromFields<T1,T2,T3,T4>(this object obj, T1 obj1, T2 obj2, T3 obj3, T4 obj4) {
    int hashCode = _seedPrimeNumber;
    if(obj1 != null)
        hashCode *= _fieldPrimeNumber + obj1.GetHashCode();
    if(obj2 != null)
        hashCode *= _fieldPrimeNumber + obj2.GetHashCode();
    if(obj3 != null)
        hashCode *= _fieldPrimeNumber + obj3.GetHashCode();
    if(obj4 != null)
        hashCode *= _fieldPrimeNumber + obj4.GetHashCode();
    return hashCode;
}
 2
Author: Jonathan Rupp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 19:03:38

Napisałem kilka rzeczy jakiś czas temu, że możesz rozwiązać swój problem... (I faktycznie, prawdopodobnie można by to poprawić, aby uwzględnić nasiona, które masz...)

W każdym razie projekt nazywa się Essence ( http://essence.codeplex.com / ) i korzysta z systemu.Linq.Biblioteki wyrażeń do generowania (na podstawie atrybutów) standardowych reprezentacji Equals/GetHashCode/CompareTo / ToString, a także możliwość tworzenia klas Ieequalitycomparer i IComparer w oparciu o lista argumentów. (Mam też kilka dalszych pomysłów, ale chciałbym uzyskać opinie społeczności, zanim przejdę dalej.)

(oznacza to, że jest prawie tak szybki, jak jest napisany odręcznie - głównym, gdzie go nie ma, jest CompareTo (); cause the Linq.Expressions nie ma pojęcia o zmiennej w wydaniu 3.5 - więc musisz wywołać CompareTo() na podstawowym obiekcie dwa razy, jeśli nie masz dopasowania. Korzystanie z rozszerzeń DLR do Linq.Wyrażenia rozwiązują to. I Załóżmy, że mógłbym użyć emit il, ale nie byłem wtedy tak zainspirowany.)

To dość prosty pomysł, ale nie widziałem go wcześniej.

Teraz rzecz w tym, że trochę straciłem zainteresowanie dopracowaniem tego (co obejmowałoby napisanie artykułu dla codeproject, dokumentowanie części kodu itp.), ale mogę być do tego przekonany, jeśli uważasz, że byłoby to coś interesującego.

(strona codeplex nie ma pakietu do pobrania; po prostu przejdź do źródła i weź to-oh, jest napisane w f# (chociaż cały kod testowy jest w c#) , ponieważ to było coś, czego chciałem się nauczyć.)

W każdym razie, oto przykład c# z testu w projekcie:

    // --------------------------------------------------------------------
    // USING THE ESSENCE LIBRARY:
    // --------------------------------------------------------------------
    [EssenceClass(UseIn = EssenceFunctions.All)]
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence>
    {
        [Essence(Order=0] public int MyInt           { get; set; }
        [Essence(Order=1] public string MyString     { get; set; }
        [Essence(Order=2] public DateTime MyDateTime { get; set; }

        public override int GetHashCode()                                { return Essence<TestEssence>.GetHashCodeStatic(this); }
    ...
    }

    // --------------------------------------------------------------------
    // EQUIVALENT HAND WRITTEN CODE:
    // --------------------------------------------------------------------
    public class TestManual
    {
        public int MyInt;
        public string MyString;
        public DateTime MyDateTime;

        public override int GetHashCode()
        {
            var x = MyInt.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= (MyString == null) ? 0 : MyString.GetHashCode();
            x *= Essence<TestEssence>.HashCodeMultiplier;
            x ^= MyDateTime.GetHashCode();
            return x;
        }
    ...
    }

W każdym razie, projekt, jeśli ktoś uważa, że jest wartościowy, wymaga dopracowania, ale pomysły są...

 3
Author: Paul Westcott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-11 09:48:50

Wygląda mi całkiem dobrze, mam tylko jeden problem: szkoda, że musisz użyć object[], aby przekazać wartości, ponieważ będzie to blokować wszystkie typy wartości, które wysyłasz do funkcji. Nie sądzę jednak, że masz duży wybór, chyba że wybierzesz drogę tworzenia ogólnych przeciążeń, tak jak sugerowali inni.

 1
Author: Andrew Hare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 20:12:10

Zgodnie z ogólną zasadą powinieneś zawęzić swoją unchecked tak wąsko, jak tylko możesz, choć nie ma to tu większego znaczenia. Poza tym, wygląda dobrze.

 0
Author: chaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 16:40:58
public override int GetHashCode() {
    return this.GetHashCodeFromFields(field1, field2, field3, this);
}

(tak, jestem bardzo pedantyczny, ale to jest jedyny problem, który widzę)

 0
Author: dfa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-18 16:47:15

Bardziej optymalne:

  1. Utwórz generator kodu, który używa odbicia do przeglądania pól obiektów biznesowych i tworzy nową klasę częściową, która nadpisuje GetHashCode () (i Equals ()).
  2. Uruchom Generator kodu, gdy program uruchomi się w trybie debugowania, a jeśli Kod się zmienił, zakończ z Komunikatem do dewelopera, aby przekompilować.

Zalety tego rozwiązania to:

  • używając reflection wiesz, które pola są typami wartości, a co za tym idzie czy potrzebują kontroli zerowej.
  • nie ma żadnych kosztów ogólnych - żadnych dodatkowych wywołań funkcji, żadnych konstrukcji listy itp. Jest to ważne, jeśli robisz wiele wyszukiwania słownika.
  • Długie implementacje (w klasach z dużą ilością pól) są ukryte w klasach częściowych, z dala od Twojego ważnego kodu biznesowego.

Wady:

  • przesada, jeśli nie wykonasz wielu wyszukiwań/wywołań słownika w GetHashCode ().
 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-19 01:33:40

Powinienem zwrócić uwagę, że prawie nigdy nie powinno się przydzielać podczas implementacji GetHashCode (oto Niektóre przydatne blog posty na ten temat).

Sposób działania params (generowanie nowej tablicy w locie) oznacza, że nie jest to dobre ogólne rozwiązanie. Lepiej byłoby użyć wywołania metody dla każdego pola i zachować stan hash jako zmienną przekazaną do nich(ułatwia to Korzystanie z lepszych funkcji mieszających i lawinowania).

 0
Author: ShuggyCoUk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-19 09:22:34

Poza problemami wynikającymi z używania params object[] fields, myślę, że niestosowanie informacji o typie może być problemem wydajnościowym również w niektórych sytuacjach. Załóżmy, że dwie klasy A, B mieć ten sam typ i liczbę pól oraz zaimplementować ten sam interfejs I. Teraz, jeśli umieścisz A i B obiekty na Dictionary<I, anything> obiekty o równych polach i różnych typach wylądują w tym samym wiadrze. Pewnie wstawiłbym jakieś stwierdzenie w stylu hashCode ^= GetType().GetHashCode();

Jonathan Rupp zaakceptował odpowiedź na params array, ale nie zajmują się boksem typów wartości. Tak więc, jeśli wydajność jest bardzo ważna, prawdopodobnie zadeklarowałbym GetHashCodeFromFields, że nie ma obiektu, a parametry int i wysłał nie same pola, ale kody hashowe pól. tj.

public override int GetHashCode() 
{
    return this.GetHashCodeFromFields(field1.GetHashCode(), field2.GetHashCode());
}
 0
Author: Ali Ferhat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-25 10:49:38

Jeden problem, który może się pojawić, to gdy mnożenie trafi 0, końcowy hashCode jest zawsze 0, jak właśnie doświadczyłem z obiektem o wielu właściwościach, w następującym kodzie:

hashCode *= _fieldPrimeNumber + fields[i].GetHashCode();

Proponuję:

hashCode = hashCode * _fieldPrimeNumber + fields[i].GetHashCode();

Lub coś podobnego z xor jak to :

hashCode = hashCode * _fieldPrimeNumber ^ fields[i].GetHashCode();
 0
Author: McX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:23:34