c# - Override GetHashCode dell'oggetto contenente array generico




arrays generics (6)

Ho una classe che contiene le seguenti due proprietà:

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

L'ho reso IEquatable<T> e sovrascritto l' object.Equals come questo:

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);
}

Quando si object.Equals override di object.Equals devo anche sovrascrivere GetHashCode . Ma quale codice dovrei implementare? Come posso creare un hashcode da un array generico? E come posso combinarlo con l'intero Id ?

public override int GetHashCode()
{
    return // What?
}

A causa dei problemi sollevati in questo thread, sto postando un'altra risposta che mostra cosa succede se ti capita male ... principalmente, che non puoi usare il GetHashCode() dell'array; il comportamento corretto è che nessun avviso viene stampato quando lo si esegue ... cambia i commenti per correggerlo:

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);
    }
}

A condizione che Id e Valori non cambieranno mai, e Valori non è nullo ...

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

Nota che la tua classe non è immutabile, poiché chiunque può modificare il contenuto di Valori perché è una matrice. Detto questo, non proverei a generare un hashcode usando il suo contenuto.


Dato che hashCode è un po 'una chiave per la memorizzazione dell'oggetto (come in un hashtable), vorrei usare solo Id.GetHashCode ()


FWIW, è molto pericoloso usare il contenuto dei Valori nel tuo codice hash. Dovresti farlo solo se puoi garantire che non cambierà mai. Tuttavia, poiché è esposto, non credo che sia possibile. L'hashcode di un oggetto non dovrebbe mai cambiare. Altrimenti, perde il suo valore come chiave in un Hashtable o in un dizionario. Considera il bug difficile da trovare nell'usare un oggetto come chiave in un Hashtable, il suo codice hash cambia a causa di un'influenza esterna e non puoi più trovarlo in Hashtable!


Lo farei in questo modo:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;

So che questo thread è piuttosto vecchio, ma ho scritto questo metodo per permettermi di calcolare hashcode di più oggetti. È stato molto utile in questo caso. Non è perfetto, ma soddisfa le mie esigenze e probabilmente anche le tue.

Non posso davvero crederci. Ho ottenuto il concetto da alcune delle implementazioni di .net gethashcode. Sto usando 419 (dopotutto, è il mio primo grande preferito), ma puoi scegliere praticamente un numero primo ragionevole (non troppo piccolo ... non troppo grande).

Quindi, ecco come ottengo i miei hashcode:

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());
        }
    }
}




hashcode