2010-03-30 17 views
49

Ho cercato le linee guida di override per le strutture, ma tutto quello che posso trovare è per le classi.Metodo Overriding Equals in Structures

All'inizio pensavo che non avrei dovuto verificare se l'oggetto passato era nullo, poiché le strutture sono tipi di valore e non possono essere nulli. Ma ora che vengo a pensarci bene, da pari a pari firma è

public bool Equals(object obj) 

sembra che non v'è nulla impedendo all'utente del mio struct essere cercando di confrontarlo con un tipo di riferimento arbitrario.

Il mio secondo punto riguarda il casting che I (think I) deve fare prima di confrontare i miei campi privati ​​nella mia struct. Come dovrei lanciare l'oggetto sul tipo della mia struct? La parola chiave as di C# sembra adatta solo per i tipi di riferimento.

+6

Solo una nota che si sono incoraggiati ad evitare le strutture mutevoli in .Net. È impostato in modo da rispettare i tipi di riferimento (classi) la maggior parte del tempo e utilizzare le strutture solo raramente. –

+4

Secondo. Usa le strutture immutabili * senza * sottotipi. Allora Equals e == dovrebbero essere gli stessi per un dato ricevitore (valore del lato sinistro) dove l'unica differenza nell'implementazione è Equals ha bisogno di un controllo 'is' e quindi, per semplicità, invia a ==. Pertanto entrambi i contratti sono soddisfatti e le sorprese vengono mitigate. –

+0

Sì, questa struttura è immutabile. Sto solo confrontando un int. –

risposta

64
struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct)) 
      return false; 

     MyStruct mys = (MyStruct) obj; 
     // compare elements here 

    } 

} 
+1

Puoi aggiungere la parola chiave 'override' qui per chiarezza? Sempre buona pratica in Java, dovrebbe essere la stessa in C#. –

+1

Vedere anche le linee guida di Microsoft - http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx – yoyo

+7

@JohanS In C# è più che una buona pratica, se si omette l'override 'il metodo in realtà fa qualcosa di completamente diverso. – Pharap

5

Utilizzare l'operatore is:

public bool Equals(object obj) 
{ 
    if (obj is MyStruct) 
    { 
    var o = (MyStruct)obj; 
    ... 
    } 
} 
0

aggiunta alle risposte esistenti.

È possibile avere ancora valori nullable se si aggiunge un? dopo il nome struct (questo funziona per ogni oggetto di valore)

int? 

Casting è anche fatto chiamando (MyStructName)variableName

+2

È possibile, ma i valori nulli hanno una penalizzazione delle prestazioni molto elevata che oltrepassa i benefici che si otterrebbero utilizzando "come" anziché "è". –

+0

@DanStory Non sarei così veloce. Se ti interessa dare un'occhiata a [questo] (http://stackoverflow.com/a/28281410), sarei curioso di sapere se c'è qualcosa che ho perso. tl; dr: is + cast compila un po 'più bello, ma sembra che non ci sia nulla di simile a una "penalità dalle prestazioni molto alte" al + al pugilato. In effetti, non posso rendere il cast di + è più veloce in modo affidabile (a volte il metodo di inscatolamento come + prenderà il comando). – tne

+0

@DanStory Ho decisamente sbagliato su quel commento precedente. La penalità * è * alta (rispetto all'alternativa in un microbenchmark comunque). La stessa risposta collegata è stata modificata. – tne

12

Suppongo che, se si sta usando .NET 4,5, si può usare l'implementazione di default come indicato nel documentation:

Quando si definisce il proprio tipo, che tipo di eredita le funzionalità definito dal metodo Equals del suo tipo di base.

ValueType.Equals: Equality di valore; confronto diretto byte per byte o confronto campo per campo mediante riflessione.

+1

In realtà è precedente alla 4.5, non so quando è stato aggiunto ma è sicuramente disponibile in 4. Anche se un commento su MSDN sembra indicare che potrebbe non essere preciso per i tipi a virgola mobile. – Pharap

+1

Vedere http://stackoverflow.com/q/1009394 per considerazioni sulle prestazioni. – tne

+0

@Pharap: quando si definisce 'Equals', Microsoft non ha chiarito come dovrebbero essere le cose" uguali "per restituire' true'; ci sono alcuni contesti in cui è utile verificare i valori in virgola mobile per * l'equivalenza * (che lo zero positivo e negativo sono numericamente uguali non implica che siano equivalenti, poiché se x e y sono equivalenti ciò dovrebbe implicare che 1/x = = 1/a, ma non è vero per lo zero positivo e negativo). I valori in virgola mobile in alcune strutture sono testati per l'equivalenza, ma non conosco mezzi generali per richiedere un tale test. – supercat

6

Nel caso qualcuno sta chiedendo circa il calo di prestazioni di boxe struct in un oggetto Nullable (per evitare il tipo di controllo da doppia is e cast), non v'è un overhead non trascurabile.

tl; dr: utilizzare is & espressi in questo scenario.

struct Foo : IEquatable<Foo> 
{ 
    public int a, b; 

    public Foo(int a, int b) 
    { 
     this.a = a; 
     this.b = b; 
    } 

    public override bool Equals(object obj) 
    { 
#if BOXING 
     var obj_ = obj as Foo?; 
     return obj_ != null && Equals(obj_.Value); 
#elif DOUBLECHECK 
     return obj is Foo && Equals((Foo)obj); 
#elif MAGIC 
     ? 
#endif 
    } 

    public bool Equals(Foo other) 
    { 
     return a == other.a && b == other.b; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     RunBenchmark(new Foo(42, 43), new Foo(42, 43)); 
     RunBenchmark(new Foo(42, 43), new Foo(43, 44)); 
    } 

    static void RunBenchmark(object x, object y) 
    { 
     var sw = Stopwatch.StartNew(); 
     for (var i = 0; i < 100000000; i++) x.Equals(y); 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

Risultati:

BOXING 
EQ 8012 7973 7981 8000 
NEQ 7929 7715 7906 7888 

DOUBLECHECK 
EQ 3654 3650 3638 3605 
NEQ 3310 3301 3319 3297 

Attenzione: Questo test potrebbe essere viziata in molti modi, anche se ho fatto verificare che il codice di riferimento in sé non è stato ottimizzato in modo strano.

Guardando l'IL, il metodo di controllo doppio compila un po 'più pulito.

Boxe IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 37 (0x25) 
    .maxstack 2 
    .locals init (
     [0] valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> obj_ 
    ) 

    IL_0000: ldarg.1 
    IL_0001: isinst valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_0006: unbox.any valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo> 
    IL_000b: stloc.0 
    IL_000c: ldloca.s obj_ 
    IL_000e: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_HasValue() 
    IL_0013: brfalse.s IL_0023 

    IL_0015: ldarg.0 
    IL_0016: ldloca.s obj_ 
    IL_0018: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype StructIEqualsImpl.Foo>::get_Value() 
    IL_001d: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0022: ret 

    IL_0023: ldc.i4.0 
    IL_0024: ret 
} // end of method Foo::Equals 

doppio controllo IL:

.method public hidebysig virtual 
    instance bool Equals (
     object obj 
    ) cil managed 
{ 
    // Method begins at RVA 0x2060 
    // Code size 23 (0x17) 
    .maxstack 8 

    IL_0000: ldarg.1 
    IL_0001: isinst StructIEqualsImpl.Foo 
    IL_0006: brfalse.s IL_0015 

    IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: unbox.any StructIEqualsImpl.Foo 
    IL_000f: call instance bool StructIEqualsImpl.Foo::Equals(valuetype StructIEqualsImpl.Foo) 
    IL_0014: ret 

    IL_0015: ldc.i4.0 
    IL_0016: ret 
} // end of method Foo::Equals 

Props a Roman Reiner per avvistare un errore che davvero non mi faceva guardare bene.

+0

Il test * è * imperfetto! Il tuo punto di riferimento sta chiamando il metodo 'Foo.Equals (Foo)'. 'Foo.Equals (object)' non viene mai eseguito. –

+0

@RomanReiner: Oh, la vergogna. Ovviamente intendevo lanciare gli oggetti e semplicemente ho dimenticato; con grandi conseguenze (i risultati effettivi sono molto diversi) - beh, se qualcuno attribuisce comunque importanza ai microbenchmarks. Molte grazie! – tne

1

Grazie alla some news in C# 7.0 c'è un modo più semplice per ottenere la stessa risposta accettata:

struct MyStruct 
{ 
    public override bool Equals(object obj) 
    { 
     if (!(obj is MyStruct mys)) // type pattern here 
      return false; 

     return this.field1 == mys.field1 && this.field2 == mys.field2 // mys is already known here without explicit casting 
    } 
} 

o il mio preferito - la stessa funzione di corposo espressione:

struct MyStruct 
{ 
    public override bool Equals(object obj) => 
     obj is MyStruct mys 
      ? true // the initial "true" doesn't affect the overall boolean operation yet allows nice line aligning below 
       && this.field1 == mys.field1 
       && this.field2 == mys.field2 
      : false; // obj is not MyStruct 
}