L'importanza di definire il metodo GetHashCode

di Marco De Sanctis, in ASP.NET 2.0, UserScript,

Il metodo virtuale GetHashCode riveste una particolare importanza all'interno del .NET Framework, in quanto è utilizzato da un gran numero di classi per verificare l'uguaglianza di due oggetti: tanto per fare un esempio, quando aggiungiamo un oggetto ad un Dictionary o ad una Hashtable, esso viene indicizzato in base all'hash code dell'oggetto usato come chiave.

Per questa ragione, ogni volta che ci troviamo a dover ridefinire il metodo Equals, veniamo invitati dal compilatore a fare altrettanto con GetHashCode, in modo da assicurare che due oggetti presentino Hash identici se e solo se ritenuti uguali.

Ma quale può essere una corretta implementazione di GetHashCode? L'assembly System.Web contiene una classe chiamata HashCodeCombiner che permette, in maniera semplice, di generare Hash di oggetti composti. Essendo purtroppo definita come internal, è necessario invocarla via reflection. Può tornare utile, a questo proposito, realizzare un wrapper che, oltre a renderne semplice l'utilizzo, effettui un caching dei membri letti dai metadati in modo da non penalizzare troppo le prestazioni.

public class HashCodeCombiner
{
    private object internalCombiner;
    private static ConstructorInfo constructor;
    private static MethodInfo addObject;
    private static PropertyInfo resultProperty;

    private const BindingFlags flags = BindingFlags.NonPublic | 
                                       BindingFlags.Public | 
                                       BindingFlags.Instance;
    
    static HashCodeCombiner()
    {
        Assembly asm = Assembly.GetAssembly(typeof (System.Web.UI.Page));
        Type hashCodeCombinerType = 
            asm.GetType("System.Web.Util.HashCodeCombiner");
        constructor = hashCodeCombinerType.GetConstructor(
            flags, null, Type.EmptyTypes, null);
        addObject = hashCodeCombinerType.GetMethod(
            "AddObject", flags, null, new Type[] {typeof (object)}, null);
        resultProperty = hashCodeCombinerType.GetProperty(
            "CombinedHash32", flags);
    }

    public HashCodeCombiner()
    {
        internalCombiner = constructor.Invoke(null);
    }
    
    public void AddObject(object value)
    {
        addObject.Invoke(internalCombiner, new object[] {value});
    }
    
    public int CombinedHash32
    {
        get
        {
            return (int) resultProperty.GetValue(internalCombiner, null);
        }
    }
}

Il costruttore statico si occupa di inizializzare tutti i MemberInfo, in modo che sia necessario effettuare la lettura dei metadati dall'assembly una sola volta, mentre il metodo AddObject e la proprietà CombinedHash32 costituiscono la vera e propria interfaccia per l'utilizzatore. A questo punto il calcolo di un HashCode diviene questione di poche righe di codice:

public override int GetHashCode()
{
    HashCodeCombiner combiner = new HashCodeCombiner();
    combiner.AddObject(field1);
    combiner.AddObject(field2);
    ...
    return combiner.CombinedHash32;
}

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi