GetHashCode nadpisuje obiekt zawierający tablicę generyczną

Mam klasę, która zawiera następujące dwie właściwości:

public int Id      { get; private set; }
public T[] Values  { get; private set; }

Zrobiłem to IEquatable<T> i nadpisałem object.Equals tak:

public override bool Equals(object obj)
{
    return Equals(obj as SimpleTableRow<T>);
}

public bool Equals(SimpleTableRow<T> other)
{
    // Check for null
    if(ReferenceEquals(other, null))
        return false;

    // Check for same reference
    if(ReferenceEquals(this, other))
        return true;

    // Check for same Id and same Values
    return Id == other.Id && Values.SequenceEqual(other.Values);
}

Kiedy mam nadpisać object.Equals muszę również nadpisać GetHashCode oczywiście. Ale jaki kod powinienem zaimplementować? Jak utworzyć hashcode z tablicy generycznej? I jak połączyć go z Id liczbą całkowitą?

public override int GetHashCode()
{
    return // What?
}
Author: AGB, 2009-03-12

9 answers

Ze względu na problemy poruszone w tym wątku, zamieszczam kolejną odpowiedź pokazującą, co się stanie, jeśli się pomylisz... głównie, że nie można używać tablicy GetHashCode(); poprawne zachowanie polega na tym, że podczas jej uruchamiania nie są wyświetlane żadne ostrzeżenia... Przełącz komentarze, aby to naprawić:

using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
    static void Main()
    {
        // first and second are logically equivalent
        SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
            second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);

        if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
        { // proven Equals, but GetHashCode() disagrees
            Console.WriteLine("We have a problem");
        }
        HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
        set.Add(first);
        set.Add(second);
        // which confuses anything that uses hash algorithms
        if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
    }
}
class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
{

    public SimpleTableRow(int id, params T[] values) {
        this.Id = id;
        this.Values = values;
    }
    public int Id { get; private set; }
    public T[] Values { get; private set; }

    public override int GetHashCode() // wrong
    {
        return Id.GetHashCode() ^ Values.GetHashCode();
    }
    /*
    public override int GetHashCode() // right
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }
    */
    public override bool Equals(object obj)
    {
        return Equals(obj as SimpleTableRow<T>);
    }
    public bool Equals(SimpleTableRow<T> other)
    {
        // Check for null
        if (ReferenceEquals(other, null))
            return false;

        // Check for same reference
        if (ReferenceEquals(this, other))
            return true;

        // Check for same Id and same Values
        return Id == other.Id && Values.SequenceEqual(other.Values);
    }
}
 77
Author: Marc Gravell,
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-03-12 15:16:08

FWIW, używanie zawartości wartości w Twoim kodzie hashowym jest bardzo niebezpieczne. Możesz to zrobić tylko wtedy, gdy możesz zagwarantować, że to się nigdy nie zmieni. Jednak, ponieważ jest on narażony, nie sądzę, aby gwarantowanie tego było możliwe. Hashcode obiektu nigdy nie powinien się zmieniać. W przeciwnym razie traci swoją wartość jako klucz w Hashtable lub słowniku. Zastanów się nad trudnym do znalezienia błędem polegającym na używaniu obiektu jako klucza w Hashtable, jego hashcode zmienia się z powodu wpływu zewnętrznego i nie możesz już znajdź go w Hashtable!

 28
Author: Dustin Campbell,
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
2015-03-25 20:26:02

Ponieważ hashCode jest kluczem do przechowywania obiektu (lllike w hashtable), użyłbym just Id.GetHashCode ()

 3
Author: Jhonny D. Cano -Leftware-,
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-03-12 14:19:25

A może coś w stylu:

    public override int GetHashCode()
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }

Powinno to być zgodne z SequenceEqual, zamiast porównywania referencji na tablicy.

 2
Author: Marc Gravell,
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-03-12 14:14:13
public override int GetHashCode() {
   return Id.GetHashCode() ^ Values.GetHashCode();  
}

Jest kilka dobrych punktów w komentarzach i innych odpowiedziach. OP powinien rozważyć, czy wartości zostaną użyte jako część "klucza", jeśli obiekt zostanie użyty jako klucz w słowniku. Jeśli tak, to powinny być częścią kodu hashowego, w przeciwnym razie nie.

Z drugiej strony, nie jestem pewien, dlaczego metoda GetHashCode powinna odzwierciedlać SequenceEqual. Ma na celu obliczenie indeksu w tabelę hash, a nie całkowite wyznaczenie równości. Jeśli jest wiele hash kolizje tabeli za pomocą powyższego algorytmu, a jeśli różnią się one sekwencją wartości, to należy wybrać algorytm uwzględniający sekwencję. Jeśli kolejność nie ma znaczenia, oszczędzaj czas i nie bierz go pod uwagę.

 1
Author: John Saunders,
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-03-03 13:31:53

Zrobiłbym to w ten sposób:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;
 0
Author: Grzenio,
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-03-12 14:13:23

Wiem, że ten wątek jest dość stary, ale napisałem tę metodę, aby umożliwić mi obliczanie hashcodów wielu obiektów. To było bardzo pomocne w tej sprawie. Nie jest idealny, ale spełnia moje potrzeby i prawdopodobnie twoje.

Nie mogę sobie za to przypisać. Pomysł zaczerpnąłem z niektórych implementacji. NET gethashcode. Używam 419 (W końcu to mój ulubiony large prime), ale możesz wybrać dowolny rozsądny prime (nie za mały . . . nie zbyt duży).

Oto jak zdobędę moje hashcodes:

using System.Collections.Generic;
using System.Linq;

public static class HashCodeCalculator
{
    public static int CalculateHashCode(params object[] args)
    {
        return args.CalculateHashCode();
    }

    public static int CalculateHashCode(this IEnumerable<object> args)
    {
        if (args == null)
            return new object().GetHashCode();

        unchecked
        {
            return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
        }
    }
}
 0
Author: D. Patrick,
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
2010-12-18 19:19:16

Pod warunkiem, że Id i wartości nigdy się nie zmienią, a wartości nie będą równe null...

public override int GetHashCode()
{
  return Id ^ Values.GetHashCode();
}

Zauważ, że twoja klasa nie jest niezmienna, ponieważ każdy może zmodyfikować zawartość wartości, ponieważ jest to tablica. Biorąc to pod uwagę, nie próbowałbym wygenerować hashcode za pomocą jego zawartości.

 0
Author: Dustin Campbell,
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-11-17 15:09:49

Po prostu musiałem dodać kolejną odpowiedź, ponieważ jedno z bardziej oczywistych (i najłatwiejszych do wdrożenia) rozwiązań nie zostało wymienione - Nie włączając kolekcji w twoje GetHashCode obliczenia!

Najważniejszą rzeczą, która zdawała się tu zapomnieć, jest to, że wyjątkowość wyniku GetHashCode nie jest wymagana (lub w wielu przypadkach nawet możliwa). Nierówne obiekty nie muszą zwracać nierównych kodów hash, jedynym wymogiem jest, aby równe obiekty zwracały równe kody hash. Więc zgodnie z tą definicją, implementacja GetHashCode jest poprawna dla wszystkich obiektów (zakładając, że istnieje poprawna implementacja Equals):

public override int GetHashCode() 
{ 
    return 42; 
} 

Oczywiście dałoby to najgorszą możliwą wydajność w wyszukiwaniu hashtable, O (n) zamiast O (1), ale nadal jest poprawne funkcjonalnie.

Mając to na uwadze, moim ogólnym zaleceniem przy implementacji GetHashCode dla obiektu, który ma jakikolwiek zbiór jako jeden lub więcej jego członków, jest po prostu zignorowanie ich i obliczenie GetHashCode wyłącznie na podstawie Pozostałe Skalary. Działa to całkiem dobrze, chyba że umieścisz w tabeli skrótów ogromną liczbę obiektów, w których wszystkie ich elementy skalarne mają identyczne wartości, co skutkuje identycznymi kodami skrótów.

Ignorowanie elementów kolekcji przy obliczaniu kodu skrótu może również przynieść poprawę wydajności, pomimo zmniejszonego rozkładu wartości kodu skrótu. Pamiętaj, że użycie kodu hashowego ma na celu poprawę wydajności w tabeli hash, ponieważ nie wymaga wywoływania Equals N razy, a zamiast tego wymaga tylko wywołania GetHashCode raz i szybkiego wyszukiwania tabeli hash. Jeśli każdy obiekt ma wewnętrzną tablicę z 10 000 pozycji, które wszystkie biorą udział w obliczaniu kodu hash, wszelkie korzyści uzyskane przez dobrą dystrybucję prawdopodobnie zostaną utracone. byłoby lepiej mieć nieco mniej rozproszony kod hashowy, jeśli generowanie go jest znacznie mniej kosztowne.

 0
Author: Allon Guralnek,
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
2012-08-21 16:26:14