2010-12-12 14 views
10

Qual è il tuo approccio allo scrivere i controlli di uguaglianza per lo structs e classes che hai creato?Controllo di uguaglianza C#

1) Fa l'uguaglianza "pieno" controllo richiedono che gran parte del codice standard (come override Equals, override GetHashCode, generico Equals, operator==, operator!=)?

2) Si specifica esplicitamente che le classi modellano l'interfaccia IEquatable<T>?

3) fare ho capito bene, che non v'è alcun modo attuale di applicare automaticamente Equals le sostituzioni, quando ho richiamare qualcosa come a == b e ho sempre dovuto implementare entrambe le Equals e operator== membri?

risposta

20

Hai ragione, questo è un sacco di codice caldaia-piatto, ed è necessario implementare tutto separatamente.

lo farei consiglia:

  • Se avete intenzione di implementare l'uguaglianza valore, ignorare GetHashCode e Equals(object) - creare sovraccarichi per == e l'attuazione IEquatable<T> senza fare che potrebbe provocare un comportamento molto inaspettato
  • vorrei sempre implementare IEquatable<T> se siete sovrascrivendo Equals(object) e GetHashCode
  • ho solo sovraccaricare l'operatore == più raramente
  • concernente la parità in modo corretto per le classi non sigillate è difficile, e può ancora produrre risultati sorprendenti/indesiderati. Se avete bisogno di uguaglianza per i tipi in una gerarchia, implementare IEqualityComparer<T> esprimendo il confronto che ti interessa.
  • uguaglianza per i tipi mutabili di solito è una cattiva idea, come due oggetti possono essere uguali, e quindi diseguale dopo ...se un oggetto è mutato (in un modo che influenza l'uguaglianza) dopo che è stato usato come chiave in una tabella hash, non sarà più possibile ritrovarlo.
  • Parte della piastra della caldaia è leggermente diversa per le strutture ... ma come Marc, scrivo molto raramente le mie strutture.

Ecco un esempio di implementazione:

using System; 

public sealed class Foo : IEquatable<Foo> 
{ 
    private readonly string name; 
    public string Name { get { return name; } } 

    private readonly int value; 
    public int Value { get { return value; } } 

    public Foo(string name, int value) 
    { 
     this.name = name; 
     this.value = value; 
    } 

    public override bool Equals(object other) 
    { 
     return Equals(other as Foo); 
    } 

    public override int GetHashCode() 
    { 
     int hash = 17; 
     hash = hash * 31 + (name == null ? 0 : name.GetHashCode()); 
     hash = hash * 31 + value; 
     return hash; 
    } 

    public bool Equals(Foo other) 
    { 
     if ((object) other == null) 
     { 
      return false; 
     } 
     return name == other.name && value == other.value; 
    } 

    public static bool operator ==(Foo left, Foo right) 
    { 
     return object.Equals(left, right); 
    } 

    public static bool operator !=(Foo left, Foo right) 
    { 
     return !(left == right); 
    } 
} 

E sì, questo è un diavolo di un sacco di testo standard, molto poco di che cambia tra le implementazioni :(

L'attuazione di == è leggermente meno efficiente di quanto potrebbe essere, in quanto chiamerà fino al Equals(object) che deve fare il controllo dinamico del tipo ... ma l'alternativa è ancora più piastra della caldaia, come questa:

public static bool operator ==(Foo left, Foo right) 
{ 
    if ((object) left == (object) right) 
    { 
     return true; 
    } 

    // "right" being null is covered in left.Equals(right) 
    if ((object) left == null) 
    { 
     return false; 
    } 
    return left.Equals(right); 
} 
+1

^^ Il tuo ogni post è un capitolo di apprendimento in C# .. :) – Dienekes

+0

2 suggerimenti minori per il secondo blocco di codice: 1) Non dovresti spostare '(object) left == (object) right' da' == 'a generico' Equals'? In modo che dia la velocità (ovviamente dipende, ma assumendo il caso peggiore) il controllo dell'eguaglianza di riferimento anche per il metodo generico 'Equals'? 2) non è necessario il secondo controllo nullo '(oggetto) a destra == null' in' == 'come lo si fa essenzialmente in generico' Equals'. Guarda il mio post .. – nawfal

+0

@nawfal: Non penso che ci sia molto da fare nel caso generico di 'Equals' - sarà comunque veloce nei casi in cui * è * vero, e nei casi in cui * non è * vero, sta aggiungendo un assegno extra senza alcun beneficio. Per quanto riguarda la parte nulla - ciò richiederebbe di nuovo il controllo dinamico del tipo. Sì, potresti discutere per entrambi - ma sono abbastanza contento di ciò che ho scritto due anni fa ... –

1

È sufficiente implementare l'operatore == per a == b.
Poiché i miei dati nei dizionari mi piacciono a volte ho la precedenza su GetHashCode.
Successivamente implemento Equals (come standard non menzionato ... questo perché non c'è alcun vincolo per l'uguaglianza quando si usano i generici) e specificare l'implementazione IEquatable. Dato che lo farò, potrei anche puntare le mie implementazioni == e! = Su Equals. :)

6

Raramente faccio qualcosa di speciale per le lezioni; per la maggior parte degli oggetti regolari l'uguaglianza referenziale funziona bene.

Ancora più raramente scrivo un struct; ma dal momento che le strutture rappresentano i valori di solito è opportuno fornire l'uguaglianza, ecc. Questo di solito implica tutto; Uguale, ==,! = E IEquatable<T> (poiché questo evita la boxe negli scenari utilizzando EqualityComparer<T>.Default.

Il testo standard di solito non è troppo problematico, ma strumenti come ReSharper IIRC può aiutare qui.

Sì, è consigliabile per mantenere Equals e == in sincronia, e questo deve essere fatto esplicitamente.