2010-08-17 15 views
32

È il codice che utilizza lo static Object.Equals per verificare la nullità più robusta del codice che utilizza l'operatore == o regular Object.Equals? Non sono gli ultimi due vulnerabili a essere sovrascritti in modo tale che il controllo di null non funzioni come previsto (ad esempio restituendo false quando il valore comparato è null)?Uguale (articolo, null) o articolo == null

In altre parole, è questo:

if (Equals(item, null)) { /* Do Something */ } 

più robusto di questo:

if (item == null) { /* Do Something */ } 

Personalmente trovo quest'ultimo sintassi più facile da leggere. Dovrebbe essere evitato quando si scrive codice che gestirà oggetti al di fuori del controllo dell'autore (ad es. Librerie)? Dovrebbe essere sempre evitato (quando si verifica il null)? È solo un taglio di capelli?

risposta

43

Non c'è una risposta semplice a questa domanda. Chiunque dice che usa sempre l'uno o l'altro ti sta dando dei cattivi consigli, secondo me.

Esistono diversi metodi che è possibile chiamare per confrontare le istanze dell'oggetto. Dati due istanze di oggetti a e b, si potrebbe scrivere:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

Questi potrebbero tutti fare cose diverse!

Object.Equals(a,b) sarà (per default) eseguire confronto di uguaglianza riferimento tipi di riferimento e confronto bit per bit sui tipi di valori.Dalla documentazione MSDN:

L'implementazione predefinita di Equals sostiene l'uguaglianza di riferimento per tipi di riferimento, e l'uguaglianza bit per i tipi di valore. L'uguaglianza di riferimento significa che i riferimenti oggetto che sono confrontati con si riferiscono allo stesso oggetto. Equality bitwise significa che gli oggetti confrontati hanno la stessa rappresentazione binaria .

Si noti che un tipo derivato potrebbe sovrascrivere il metodo Equals a uguaglianza valore dell'attrezzo. Il valore equality significa che gli oggetti confrontati hanno lo stesso valore ma diverse rappresentazioni binarie .

Nota l'ultimo paragrafo sopra ... ne discuteremo un po 'più tardi.

Object.ReferenceEquals(a,b) esegue solo il confronto dell'uguaglianza di riferimento. Se i tipi passati sono tipi di valore in box, il risultato è sempre false.

a.Equals(b) chiama il metodo di istanza virtuale di Object, che il tipo di a poteva ignorare di fare tutto ciò che vuole. La chiamata viene eseguita utilizzando la distribuzione virtuale, quindi il codice che viene eseguito dipende dal tipo di esecuzione di a.

a == b richiama l'operatore di overload statico del ** fase di compilazione tipo * di a. Se l'implementazione di tale operatore richiama i metodi di istanza su a o b, potrebbe dipendere anche dai tipi di runtime dei parametri. Dal momento che l'invio si basa sui tipi nell'espressione, possono produrre risultati diversi:

Frog aFrog = new Frog(); 
Frog bFrog = new Frog(); 
Animal aAnimal = aFrog; 
Animal bAnimal = bFrog; 
// not necessarily equal... 
bool areEqualFrogs = aFrog == bFrog; 
bool areEqualAnimals = aAnimal = bAnimal; 

Quindi, sì, c'è la vulnerabilità di controllo per i null che utilizzano operator ==. In pratica, la maggior parte dei tipi non è sovraccarico == - ma non c'è mai una garanzia.

Il metodo di istanza Equals() non è migliore qui. Mentre l'implementazione predefinita esegue controlli di uguaglianza di riferimento/bit per bit, è possibile che un tipo sovrascriva il metodo membro Equals(), nel qual caso verrà chiamata questa implementazione. Un'implementazione fornita dall'utente può restituire ciò che vuole, anche quando si confronta con null.

Ma che dire della versione statica di Object.Equals() che chiedi? Questo può finire con l'esecuzione del codice utente? Bene, si scopre che la risposta è SI. L'attuazione di Object.Equals(a,b) espande per qualcosa sulla falsariga di:

((object)a == (object)b) || (a != null && b != null && a.Equals(b)) 

Si può provare questo per te:

class Foo { 
    public override bool Equals(object obj) { return true; } } 

var a = new Foo(); 
var b = new Foo(); 
Console.WriteLine(Object.Equals(a,b)); // outputs "True!" 

Di conseguenza, è possibile per l'istruzione: Object.Equals(a,b) per eseguire codice utente quando nessuno dei tipi nella chiamata sono null.Notare che Object.Equals(a,b)non chiama chiama la versione di istanza di Equals() quando uno degli argomenti è null.

In breve, il tipo di comportamento di confronto che si ottiene può variare in modo significativo, a seconda del metodo che si sceglie di chiamare. Un commento qui, tuttavia: Microsoft non documenta ufficialmente il comportamento interno di Object.Equals(a,b). Se avete bisogno di un gaurantee rivestito di confrontare un riferimento a nulla, senza qualsiasi altro codice in esecuzione di ferro, si vuole Object.ReferenceEquals():

Object.ReferenceEquals(item, null); 

Questo metodo rende l'intento extremently chiaro - che non siano specificamente aspettate il risultato di essere il confronto di due riferimenti per l'uguaglianza di riferimento. Il vantaggio qui sopra usando qualcosa come Object.Equals(a,null), è che è meno probabile che qualcuno arriverà più tardi e dire:

"Hey, questo è imbarazzante, cerchiamo di sostituirlo con: a.Equals(null) o a == null

che potenzialmente può essere diverso.

Diamo iniettare qualche pragmatismo qui, tuttavia. Finora abbiamo parlato della possibilità di diverse modalità di confronto per ottenere risultati diversi. Mentre questo è certamente il caso, ci sono alcuni tipi dove è sicuro per wr ite a == null. Le classi .NET integrate come String e Nullable<T> hanno una semantica ben definita per il confronto. Inoltre, sono sealed - impedendo qualsiasi modifica al loro comportamento attraverso l'ereditarietà. Quanto segue è abbastanza comune (e corretta):

string s = ... 
if(s == null) { ... } 

E 'inutile (e brutto) di scrivere:

if(ReferenceEquals(s,null)) { ... } 

Quindi, in alcuni casi limitati, utilizzando == è sicuro, e appropriato.

+0

Ho pensato che la mia risposta (la risposta di Microsoft per procura) è una risposta piuttosto semplice. – mquander

+4

Domande come: * "dovrei sempre/mai fare X" * implicano un vuoto di conoscenza sulle sfumature del soggetto in questione. Ho sentito che un po 'più di dettaglio sarebbe utile qui per chiarire perché non penso che una risposta semplice sia significativa. – LBushkin

+0

Il metodo statico 'object.Equals (a, b)' può, in effetti, chiamare il metodo 'Equals' sovraccarico/virtuale sull'oggetto' a'. Se ricordo correttamente, fa qualcosa come '((oggetto) a == (oggetto) b) || (a! = null && b! = null && a.Equals (b)) '. (Questo è irrilevante quando si confronta con 'null', che è ciò che l'OP chiede, ma è rilevante per il caso generale.) – LukeH

4

if (Equals(item, null)) non è più robusto di if (item == null) e trovo più confuso avviare.

+0

È possibile che qualcuno sovraccarichi l'operatore '==' senza eseguire l'override di 'Equals'. Questo sarebbe un cattivo progetto, ma è del tutto possibile che la semantica del confronto di uguaglianza differisca tra i due.Inoltre, se l'operatore '==' è sovraccarico, può fare qualcosa di completamente diverso dal metodo incorporato 'Objects.Equals()' - che credo controlli per l'uguaglianza di riferimento. – LBushkin

2

Il framework guidelines suggeriscono che si trattano Equals come l'uguaglianza valore (il controllo per vedere se due oggetti rappresentano la stessa informazione, vale a dire le proprietà di confronto), e == come l'uguaglianza di riferimento, con l'eccezione di oggetti immutabili, per il quale si dovrebbe probabilmente di override == per essere uguaglianza di valore.

Quindi, supponendo che le linee guida si applichino qui, selezionare ciò che è semanticamente ragionevole. Se hai a che fare con oggetti immutabili e prevedi che entrambi i metodi producano risultati identici, utilizzerei lo standard == per maggiore chiarezza.

+2

Le linee guida Microsoft NON risolvono il problema dei null, che è la domanda. Ancora più importante, mentre "myString == null" è un test sicuro, "myString.Equals (null)" causerà un'eccezione quando myString è nullo. Inoltre, le linee guida Microsoft non menzionano nemmeno la differenza tra Object.Equals (myObject, b) e myObject.Equals (b). Il primo è robusto; il secondo dà un'eccezione se myObject è nullo. Quindi, mentre il link che fornisci è utile, NON è una risposta alla domanda del poster. – ToolmakerSteve

1

In riferimento a "... codice di scrittura che gestirà oggetti al di fuori del controllo dell'autore ...", vorrei sottolineare che sia l'Object.Equals statico che l'operatore == sono metodi statici e quindi non possono essere virtuali/sovrascritti. Quale implementazione viene chiamata è determinata al momento della compilazione in base al tipo o ai tipi statici. In altre parole, non esiste un modo per una libreria esterna di fornire una versione diversa della routine al codice compilato.

+0

** Questa istruzione non è completamente corretta. ** È possibile per l'implementazione dell'operatore '==' su un tipo chiamare un metodo virtuale su una delle istanze coinvolte. Il che significa che * è effettivamente possibile * che le cose accadano che non sono basate sui tipi nell'espressione, ma piuttosto sui tipi di runtime coinvolti. – LBushkin

+0

E lo stesso vale per il metodo statico 'object.Equals (a, b)', che fa qualcosa come '((oggetto) a == (oggetto) b) || (a! = null && b! = null && a.Equals (b)) "cioè, potrebbe chiamare il metodo virtuale' Equals' sull'oggetto 'a'. – LukeH

+0

@LBushkin Il tuo punto è valido, tuttavia ciò non è uguale (scusate il gioco di parole) la mia affermazione è "non del tutto corretta". –

1

Quando si desidera verificare IDENTITÀ (stessa posizione in memoria):

ReferenceEquals(a, b)

Maniglie nulli. E non è superabile. Sicuro al 100%.

Ma assicuratevi davvero di volere il test di identità. Si consideri il seguente:

ReferenceEquals(new String("abc"), new String("abc"))

che restituisce false.Al contrario:

Object.Equals(new String("abc"), new String("abc"))

e

(new String("abc")) == (new String("abc"))

entrambi restituiscono true.

Se si prevede una risposta di true in questa situazione, quindi si desidera un test di UGUAGLIANZA, non un test di IDENTITÀ. Vedere la parte successiva.


Quando si desidera verificare uguaglianza (gli stessi contenuti):

  • Usa "a == b" se il compilatore non si lamenta.

  • Se questo è respinta (se il tipo di variabile a non definisce "==" operatore), quindi usare "Object.Equals(a, b)".

  • SE si è all'interno di logica in cui una è noto per non essere nullo, allora si può utilizzare il più leggibile "a.Equals(b)". Ad esempio, "this.Equals (b)" è sicuro. Oppure se "a" è un campo che è inizializzato in fase di costruzione e il costruttore genera un'eccezione se viene passato nulla come valore da utilizzare in quel campo.

Ora, per affrontare la domanda iniziale:

D: Sono questi suscettibili di essere sottoposto a override in una classe, con il codice che non gestisce nulla correttamente, con conseguente un'eccezione?

A: Sì. L'unico modo per ottenere il test di EQUALITÀ sicuro al 100% è di testare preventivamente i null.

Ma dovresti? Il bug sarebbe in quella (ipotetica futura cattiva classe), e sarebbe un tipo diretto di fallimento. Facile da correggere e correggere (da chiunque fornisca la classe). Dubito che sia un problema che accade spesso, o persiste a lungo quando succede.

Più dettagliato A: Object.Equals(a, b) è più probabile che funzioni di fronte a una classe scritta male. Se "a" è nullo, la classe Object lo gestirà da solo, quindi non vi è alcun rischio. Se "b" è nullo, il tipo DYNAMIC (runtime non compilato) di "a" determina quale metodo "uguale" viene chiamato. Il metodo chiamato deve funzionare correttamente quando "b" è nullo. A meno che il metodo chiamato non sia scritto molto male, il primo passo che fa è determinare se "b" è un tipo che comprende.

Quindi Object.Equals(a, b) è un ragionevole compromesso tra leggibilità/codifica_portata e sicurezza.