2009-10-01 5 views

risposta

70

Un esempio da MSDN

public struct Complex 
{ 
    double re, im; 
    public override bool Equals(Object obj) 
    { 
     return obj is Complex && this == (Complex)obj; 
    } 
    public override int GetHashCode() 
    { 
     return re.GetHashCode()^im.GetHashCode(); 
    } 
    public static bool operator ==(Complex x, Complex y) 
    { 
     return x.re == y.re && x.im == y.im; 
    } 
    public static bool operator !=(Complex x, Complex y) 
    { 
     return !(x == y); 
    } 
} 
+9

Per i curiosi: http://msdn.microsoft.com/en-us/library/336aedhh(v=VS.71).aspx – Mike

+0

Mi chiedo se non sarebbe meglio per le prestazioni usare 'Complesso altro = obj come Complesso' e poi controlla se 'altro == null' invece di usare' is' e poi un cast ... –

+3

@Clement: non puoi farlo per una struttura; il risultato non può essere nullo. Avresti un errore di compilazione. –

3

La differenza fondamentale tra i due è che l'operatore == è statico, cioè il metodo appropriato per invocare viene determinata in fase di compilazione, mentre il metodo Equals viene richiamato dinamicamente su un esempio.
Definire entrambi è probabilmente la cosa migliore da fare, anche se ciò è meno importante nel caso delle strutture, poiché le strutture non possono essere estese (una struct non può ereditare da un'altra).

40

È inoltre necessario implementare IEquatable < T>. Ecco un estratto da Linee guida per la progettazione di framework:

DO implement IEquatable su tipi di valore. Il metodo Object.Equals sui tipi di valore causa la boxe e la sua implementazione predefinita non è molto efficiente perché utilizza il refection. IEquatable.Equals può offrire prestazioni molto migliori e può essere implementato in modo che non provochi il pugilato.

public struct Int32 : IEquatable<Int32> { 
    public bool Equals(Int32 other){ ... } 
} 

seguono le stesse linee guida per imperativi Object.Equals quando IEquatable.Equals di attuazione. Vedere la sezione 8.7.1 per dettagliate linee guida su imperativi Object.Equals

+0

Quindi questo è usato solo sui tipi di valore? (non si fa riferimento?) – UpTheCreek

+1

Poiché i tipi di riferimento non devono essere racchiusi tra loro quando vengono trasmessi come oggetto, ergo, IEquatable non fornisce alcun vantaggio. I tipi di valore vengono solitamente copiati completamente nello stack (o nel layout dei tipi esterni), quindi per ottenere un riferimento a un oggetto e gestire correttamente la durata dell'oggetto, deve essere inserito in una scatola (avvolto con un tipo speciale) e copiato al mucchio; solo allora il riferimento all'oggetto heap può essere passato a una funzione come Object.Equals. – gimpf

12

Unfortunetely Non ho abbastanza fama di commentare altre voci. Quindi sto postando un possibile miglioramento per la soluzione migliore qui.

Correggetemi, se sbaglio, ma l'attuazione di cui sopra

public struct Complex 
{ 
    double re, im; 
    public override bool Equals(Object obj) 
    { 
     return obj is Complex && this == (Complex)obj; 
    } 
    public override int GetHashCode() 
    { 
     return re.GetHashCode()^im.GetHashCode(); 
    } 
    public static bool operator ==(Complex x, Complex y) 
    { 
     return x.re == y.re && x.im == y.im; 
    } 
    public static bool operator !=(Complex x, Complex y) 
    { 
     return !(x == y); 
    } 
} 

Ha difetto principale. Sto riferendosi a

public override int GetHashCode() 
    { 
     return re.GetHashCode()^im.GetHashCode(); 
    } 

XOR è simmetrico, così complesso (2,1) e Complex (1,2) darebbe stesso hashCode.

probabilmente dovremmo fare qualcosa di più simile a:

public override int GetHashCode() 
    { 
     return re.GetHashCode() * 17^im.GetHashCode(); 
    } 
+5

Avere collisioni hashcode non è necessariamente un problema. In effetti avrai sempre la possibilità di una collisione (leggi su buchi di pigione/paradosso di compleanno) Nel tuo caso Complesso (1,4) e Complesso (4,1) collidono (sicuramente ci sono state meno collisioni) dipende dai tuoi dati . Il codice hash viene utilizzato per estirpare rapidamente il 99,999% degli oggetti indesiderati (ad esempio, in un dizionario) Gli operatori di uguaglianza hanno l'ultima parola. – DarcyThomas

+0

È stato detto che più proprietà hai sulla struttura, c'è una maggiore possibilità di collisione. Questo potrebbe essere un algoritmo di hash migliore: http://stackoverflow.com/a/263416/309634 – DarcyThomas

0

Solo per completezza vorrei anche consigliare al sovraccarico Equals metodo:

public bool Equals(Complex other) 
{ 
    return other.re == re && other.im == im; 
} 

questo è un vero e proprio miglioramento spead come non c'è verificano boxe dell'argomento di input del metodo Equals(Object obj)

Alcune best practice per l'utilizzo dei tipi di valore:

  • renderli immutabili
  • Override Equals (quello che prende un oggetto come argomento);
  • overload È uguale a prendere un'altra istanza dello stesso tipo di valore (ad esempio * Equals (Complex other));
  • operatori di sovraccarico == e! =;
  • esclusione GetHashCode

Questo deriva da questo post: http://theburningmonk.com/2015/07/beware-of-implicit-boxing-of-value-types/

8

maggior parte del tempo si può evitare Equals e GetHashCode di esecuzione le strutture - perché non v'è un'implementazione automatica dal compilatore per i tipi di valore utilizzando bit a bit contenuto + riflessione per i membri di riferimento.

Dai un'occhiata alla quel post:! Which is best for data store Struct/Classes?

Così, per la facilità d'uso si potrebbe ancora implementare == e =.

Ma la maggior parte delle volte è possibile evitare l'implementazione di Equals e GetHashcode.
Un caso in cui è necessario implementare Equals e GetHashCode è per un campo che non si vuole prendere in considerazione.
Ad esempio un campo che varia con il passare del tempo come Age of a Person o instantSpeed ​​di un'auto (l'identità dell'oggetto non dovrebbe cambiare se si desidera ritrovarla nel dizionario nello stesso posto)

Cordiali saluti, codice migliore