2010-03-02 12 views
32

Sto lavorando con un modello di dominio e stavo pensando ai vari modi in cui dobbiamo implementare questi due metodi in .NET. Qual è la tua strategia preferita?Qual è la migliore strategia per Equals e GetHashCode?

Questo è il mio attuale implementazione:

public override bool Equals(object obj) 
    { 
     var newObj = obj as MyClass; 

     if (null != newObj) 
     { 
      return this.GetHashCode() == newObj.GetHashCode(); 
     } 
     else 
     { 
      return base.Equals(obj); 
     } 
    } 

    //Since this is an entity I can use it´s Id 
    //When I don´t have an Id I usually make a composite key of the properties 
    public override int GetHashCode() 
    { 
     return String.Format("MyClass{0}", this.Id.ToString()).GetHashCode(); 
    } 
+0

Correlato: http://stackoverflow.com/questions/2326288/implementing-ddd-entity-class-in-c –

+8

Non è possibile utilizzare i risultati di GetHashCode come unico determinante in Equals - i codici hash possono essere lo stesso quando gli oggetti sono diversi. Faresti molto meglio a confrontare i tuoi ID in Equals. Per ulteriori informazioni, vedere [Perché è importante sovrascrivere GetHashCode quando il metodo Equals è sovrascritto in C#?] (Http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode- when-equals-method-is-overriden-in-c) –

+0

Si dovrebbe tenere presente che GetHashCode() è utilizzato principalmente nel codice in cui le prestazioni sono importanti (elenchi con ricerche O (1), ecc.). La tua implementazione è già piuttosto lenta, ma potresti già accelerarla senza cambiare molto: 'return (" MyClass "+ this.Id) .GetHashCode();' (solo qualcosa che potresti voler ricordare con GetHashCode) – Aidiakapi

risposta

3

Supponendo che i casi sono uguali, perché i codici hash sono uguali è sbagliato.

penso che la realizzazione di GetHashCode è OK, ma io di solito uso una cosa simile a questa:

public override int GetHashCode() { 
    return object1.GetHashCode^intValue1^(intValue2 << 16); 
} 
+1

Conosco la vecchia domanda, ma puoi spiegare l'espressione che hai usato qui – skjagini

+0

@skjagini: Vedi [^ Operatore] (http://msdn.microsoft.com/en-us/library/zkacc7k1.aspx) - Esclusivo Oppure, e [<< Operatore] (http://msdn.microsoft.com/en-us/library/a1sway8w.aspx) - Maiusc sinistro. Esistono molti modi per combinare più valori numerici per ottenere un singolo HashCode. Esclusivo o efficace twiddles bit; tuttavia ha alcuni punti deboli. Cerca implementazioni GetHashCode, come [questo post SO] (http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode?lq=1). –

2

codici hash possono collidere in modo da non penso che siano un buon modo per confrontare l'uguaglianza. Dovresti confrontare i valori sottostanti che rendono gli oggetti "uguali". Vedi @Jon Skeet's risposta a questa domanda: What is the best algorithm for an overridden System.Object.GetHashCode? per una migliore implementazione GetHashCode se la tua uguaglianza comprende diverse proprietà. Se è solo una singola proprietà, puoi semplicemente riutilizzarla come hashcode.

+0

Hai ragione, ma secondo me, troppo prudente. I codici hash non sono assolutamente appropriati per determinare l'uguaglianza. Gli oggetti identici devono avere lo stesso codice hash, ma lo stesso codice hash può essere condiviso da oggetti diversi. Come dici tu, possono scontrarsi. –

30

Domain-Driven Design fa la distinzione tra Enti e Valore Oggetti. Questa è una buona distinzione da osservare poiché guida l'implementazione di Equals.

Le entità sono uguali se i rispettivi ID sono uguali tra loro.

Gli oggetti valore sono uguali se tutti i loro elementi (importanti) costitutivi sono uguali tra loro.

In ogni caso, l'implementazione di GetHashCode deve basarsi sugli stessi valori utilizzati per determinare l'uguaglianza. In altre parole, per le Entità, il codice hash dovrebbe essere calcolato direttamente dall'ID, mentre per gli Oggetti valore dovrebbe essere calcolato da tutti i valori costitutivi.

+0

Ti dispiacerebbe chiarire il vantaggio di avere un metodo 'Equals()' su ** Entities ** che confronta gli ID? Quali sono i casi d'uso? Ho chiesto [una domanda simile qui su SO] (http://stackoverflow.com/q/31533276/219187), ma fino ad ora non ho ricevuto una risposta che indichi chiaramente perché le entità dovrebbero essere confrontate per ID. – theDmi

+0

@theDmi - La ragione è che la definizione di un'entità si riferisce specificamente ad essa come unica e identificabile, e possibilmente controllata dal sistema. Almeno per un'entità, che contiene fondamentalmente dati e metodi per alterare i dati, ogni entità avrà un identificatore e quell'identificatore sarà unico. Se creo dieci Entità con gli stessi dati e la stessa chiamata per costruirle, l'identità sarà l'unica cosa che è unica. Questo è il motivo per cui Evans afferma che il sistema può controllare la creazione dell'identità. Personalmente, faccio sempre in modo che l'entità crei gli identificatori per supportarlo. –

3

Nessuna delle risposte qui è davvero azzeccata per me. Dato che hai già detto che non puoi usare Id per l'uguaglianza e hai bisogno di usare un pacchetto di proprietà, ecco un modo migliore per farlo. Nota: non considero questo in generale il modo migliore per implementare Equals e GetHashCode. Questa è una versione migliore del codice dell'OP.

public override bool Equals(object obj) 
{ 
    var myClass = obj as MyClass; 

    if (null != myClass) 
    { 
     // Order these by the most different first. 
     // That is, whatever value is most selective, and the fewest 
     // instances have the same value, put that first. 
     return this.Id == myClass.Id 
     && this.Name == myClass.Name 
     && this.Quantity == myClass.Quantity 
     && this.Color == myClass.Color; 
    } 
    else 
    { 
     // Not sure this makes sense! 
     return base.Equals(obj); 
    } 
} 

public override int GetHashCode() 
{ 
    int hash = 19; 
    unchecked { // allow "wrap around" in the int 
     hash = hash * 31 + this.Id; // assuming integer 
     hash = hash * 31 + this.Name.GetHashCode(); 
     hash = hash * 31 + this.Quantity; // again assuming integer 
     hash = hash * 31 + this.Color.GetHashCode(); 
    } 
    return hash; 
} 

Vedi this answer by Jon Skeet per alcuni il ragionamento dietro questo. L'uso di xor non va bene perché vari set di dati possono finire con lo stesso hash. Questo metodo wrap-around con i numeri primi (i valori seme di 19 e 31 sopra, o altri valori scelti) fa un lavoro migliore di segmentazione in "bucket" che hanno ciascuna collisioni.

Se uno qualsiasi dei vostri valori può essere nullo, vi incoraggio a riflettere attentamente su come devono essere confrontati. Potresti usare la valutazione nulla di cortocircuito e forse l'operatore di coalescenza nulla. Ma assicurati che se i valori nulli debbano essere confrontati come uguali, assegni codici hash diversi alle diverse proprietà nullable quando sono nulli.

Inoltre, non sono convinto che l'implementazione di Equals abbia senso. Quando due oggetti vengono confrontati per l'uguaglianza, prima vengono confrontati i loro valori GetHashCode. Solo se questi sono diversi è il metodo Equals eseguito (in modo che se due oggetti che hanno hash sullo stesso valore siano diversi, questo verrà rilevato).Poiché l'implementazione di GetHashCode non fa riferimento a base, non ha senso il metodo Equals.

Problemi correlati