Perché (object) 0 == (object) 0 è diverso da ((object) 0) .Equals ((object) 0)?


117

Perché le seguenti espressioni sono diverse?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

In realtà, posso capire perfettamente [1] perché probabilmente il runtime .NET sarà boxil numero intero e inizierà invece a confrontare i riferimenti. Ma perché [2] è diverso?


36
OK, ora che hai capito la risposta a questa domanda, controlla la tua comprensione prevedendo il risultato di: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Ora confrontalo con la realtà. La tua previsione era corretta? In caso contrario, puoi spiegare la discrepanza?
Eric Lippert

1
@Star, per la lettura consigliata vedi msdn.microsoft.com/en-us/library/vstudio/… per gli overload disponibili sul metodo int16aka shortEquals, quindi guarda msdn.microsoft.com/en-us/library/ms173105.aspx . Non voglio rovinare il puzzle di Eric Lippert, ma dovrebbe essere abbastanza facile da capire una volta che hai letto quelle pagine.
Sam Skuce

2
Ho pensato che questa fosse una domanda Java; almeno prima di vedere la "E" in uguale.
seteropere

4
@seteropere Java è effettivamente diverso: l'autoboxing in Java memorizza nella cache gli oggetti, quindi restituisce ((Integer)0)==((Integer)0)true.
Jules

1
Puoi anche provare IFormattable x = 0; bool test = (object)x == (object)x;. Non viene eseguita alcuna nuova boxe quando la struttura è già in una scatola.
Jeppe Stig Nielsen

Risposte:


151

Il motivo per cui le chiamate si comportano in modo diverso è che si legano a metodi molto diversi.

Il ==caso verrà associato all'operatore di uguaglianza dei riferimenti statici. Ci sono 2 intvalori boxed indipendenti creati quindi non sono lo stesso riferimento.

Nel secondo caso ci si lega al metodo dell'istanza Object.Equals. Questo è un metodo virtuale che filtrerà fino a Int32.Equalse questo controlla per un numero intero in scatola. Entrambi i valori interi sono 0, quindi sono uguali


Il ==caso non chiama Object.ReferenceEquals. Produce semplicemente l' ceqistruzione IL per eseguire un confronto dei riferimenti.
Sam Harwell

8
@ 280Z28 non è solo perché il compilatore lo integra?
markmnl

@ 280Z28 Allora? Un caso simile è che il loro metodo Boolean.ToString contiene apparentemente stringhe hardcoded all'interno della sua funzione, invece di restituire Boolean.TrueString e Boolean.FalseString esposti pubblicamente. È irrilevante; Il punto è che ==fa la stessa cosa di ReferenceEquals(su Object, comunque). È tutto solo ottimizzazione interna da parte di MS per evitare chiamate di funzioni interne non necessarie su funzioni usate spesso.
Nyerguds

6
La specifica del linguaggio C #, paragrafo 7.10.6, dice: Gli operatori di uguaglianza del tipo di riferimento predefinito sono: bool operator ==(object x, object y); bool operator !=(object x, object y);Gli operatori restituiscono il risultato del confronto dei due riferimenti per uguaglianza o non-uguaglianza. Non è un requisito che il metodo System.Object.ReferenceEqualssia utilizzato per determinare il risultato. A @markmnl: No, il compilatore C # non è in linea, è qualcosa che a volte fa il jitter (ma non in questo caso). Quindi 280Z28 è corretto, il ReferenceEqualsmetodo non è effettivamente utilizzato.
Jeppe Stig Nielsen

@ JaredPar: È interessante che la specifica lo dica, dal momento che non è così che si comporta effettivamente la lingua. Dato operatori definiti come sopra, e le variabili Cat Whiskers; Dog Fido; IDog Fred;(per interfacce non correlate ICate IDoge non classi correlate Cat:ICate Dog:IDog), il confronto Whiskers==Fidoe Whiskers==34sarebbe legale (il primo potrebbe essere solo vero se baffi e Fido erano entrambi null; il secondo potrebbe non essere vero ). In effetti, un compilatore C # rifiuterà entrambi. Whiskers==Fred;sarà vietato se Catè sigillato, ma consentito se non lo è.
supercat

26

Quando si esegue il cast del valore int 0(o qualsiasi altro tipo di valore) a object, il valore è boxed . Ogni cast in objectproduce una scatola diversa (cioè un'istanza di oggetto diversa). L' ==operatore per il objecttipo esegue un confronto dei riferimenti, quindi restituisce false poiché il lato sinistro e il lato destro non sono la stessa istanza.

D'altra parte, quando si utilizza Equals, che è un metodo virtuale, viene utilizzata l'implementazione del tipo boxed effettivo, ovvero Int32.Equals, che restituisce true poiché entrambi gli oggetti hanno lo stesso valore.


18

L' ==operatore, essendo statico, non è virtuale. Verrà eseguito il codice esatto objectdefinito dalla classe (`oggetto è il tipo in fase di compilazione degli operandi), che farà un confronto dei riferimenti, indipendentemente dal tipo di runtime di entrambi gli oggetti.

Il Equalsmetodo è un metodo di istanza virtuale. Verrà eseguito il codice definito nel tipo di runtime effettivo del (primo) oggetto, non il codice nella objectclasse. In questo caso, l'oggetto è un int, quindi eseguirà un confronto di valori, poiché questo è ciò che il inttipo definisce per il suo Equalsmetodo.


Il ==token in realtà rappresenta due operatori, uno dei quali è sovraccaricabile e uno no. Il comportamento del secondo operatore è molto diverso da quello di un sovraccarico su (oggetto, oggetto).
supercat

13

Il Equals()metodo è virtuale.
Pertanto, chiama sempre l'implementazione concreta, anche quando viene eseguito il cast del callsite object. intsostituisce il Equals()confronto in base al valore, in modo da ottenere il confronto del valore.


10

== Uso: Object.ReferenceEquals

Object.Equals confronta il valore.

Il object.ReferenceEqualsmetodo confronta i riferimenti. Quando si alloca un oggetto, si riceve un riferimento contenente un valore che ne indica la posizione di memoria oltre ai dati dell'oggetto nell'heap di memoria.

Il object.Equalsmetodo confronta il contenuto degli oggetti. Per prima cosa controlla se i riferimenti sono uguali, così come object.ReferenceEquals. Ma poi chiama i metodi derivati ​​Equals per testare ulteriormente l'uguaglianza. Guarda questo:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Sebbene si Object.ReferenceEqualscomporti come un metodo che utilizza l' ==operatore C # sui propri operandi, l'operatore dell'operatore di uguaglianza dei riferimenti C # (rappresentato dall'uso di ==tipi di operandi per i quali non è definito alcun sovraccarico) utilizza un'istruzione speciale anziché la chiamata ReferenceEquals. Inoltre, Object.ReferenceEqualsaccetterà operandi che potrebbero corrispondere solo se entrambi sono nulli, e accetterà operandi a cui è necessario forzare il tipo Objecte quindi non potrebbero corrispondere a nulla, mentre la versione con uguaglianza di riferimento di ==rifiuterebbe di compilare tale utilizzo .
supercat

9

L'operatore C # usa il token ==per rappresentare due diversi operatori: un operatore di confronto sovraccaricabile staticamente e un operatore di confronto dei riferimenti non sovraccaricabile. Quando incontra il ==token, verifica prima se esiste un sovraccarico del test di uguaglianza applicabile ai tipi di operando. Se è così, richiamerà quel sovraccarico. In caso contrario, controllerà se i tipi sono applicabili all'operatore di confronto dei riferimenti. In tal caso, utilizzerà quell'operatore. Se nessuno dei due operatori è applicabile ai tipi di operando, la compilazione fallirà.

Il codice (Object)0non si limita a un upcast Int32a Object: Int32, come tutti i tipi di valore, rappresenta in realtà due tipi, uno dei quali descrive valori e posizioni di memoria (come lo zero letterale), ma non deriva da nulla, e uno dei quali descrive accumula oggetti e deriva da Object; poiché è possibile eseguire l'upcast solo su quest'ultimo tipo Object, il compilatore deve creare un nuovo oggetto heap di quest'ultimo tipo. Ogni chiamata di (Object)0crea un nuovo oggetto heap, quindi i due operandi ==sono oggetti diversi, ciascuno dei quali, indipendentemente, incapsula il Int32valore 0.

La classe Objectnon ha sovraccarichi utilizzabili definiti per l'operatore uguale. Di conseguenza, il compilatore non sarà in grado di utilizzare l'operatore di test di uguaglianza sovraccaricato e tornerà a utilizzare il test di uguaglianza di riferimento. Poiché i due operandi si ==riferiscono a oggetti distinti, verrà segnalato false. Il secondo confronto ha esito positivo perché chiede a un'istanza di oggetto heap Int32se è uguale all'altra. Poiché quell'istanza sa cosa significa essere uguale a un'altra istanza distinta, può rispondere true.


Inoltre, ogni volta che scrivi un letterale 0nel tuo codice presumo che crei un oggetto int nell'heap per quello. Non è un riferimento univoco a un valore zero statico globale (come il modo in cui hanno creato String.Empty per evitare di creare nuovi oggetti stringa vuoti solo per inizializzare nuove stringhe) Quindi sono abbastanza sicuro che anche facendo a 0.ReferenceEquals(0)restituirà false, poiché entrambi gli 0 sono Int32oggetti appena creati .
Nyerguds

1
@Nyerguds, sono abbastanza sicuro che tutto ciò che hai detto non è corretto, riguardo a int, heap, cronologia, statistica globale, ecc. 0.ReferenceEquals(0)Fallirà perché stai cercando di chiamare un metodo su una costante di tempo di compilazione. non ci sono oggetti per appenderlo. Un int unboxed è una struttura, memorizzata nello stack. Anche int i = 0; i.ReferenceEquals(...)non funzionerà. Perché System.Int32NON eredita da Object.
Andrew Backer

@AndrewBacker, System.Int32è un struct, structè System.ValueType, che a sua volta eredita System.Object. Ecco perché c'è un ToString()metodo e un Equalsmetodo perSystem.Int32
Sebastian

1
Tuttavia, Nyerguds ha torto quando afferma che verrà creato un Int32 sull'heap, il che non è il caso.
Sebastian

@SebastianGodelet, sto ignorando le parti interne in questo. System.Int32 stesso implementa questi metodi. GetType () è esterno Object, ed è qui che ho smesso di preoccuparmi molto tempo fa. Non è mai stato necessario andare oltre. AFAIK il CLR gestisce i due tipi in modo diverso e speciale. Non è solo eredità. E ' isuno dei due tipi di dati però. Semplicemente non volevo che nessuno leggesse quel commento e andasse così fuori strada, inclusa quella stranezza sulle stringhe vuote che ignora l'internamento delle stringhe.
Andrew Backer

3

Entrambi i controlli sono diversi. Il primo verifica l' identità , il secondo l' uguaglianza . In generale due termini sono identici, se si riferiscono allo stesso oggetto. Ciò implica che sono uguali. Due termini sono uguali, se i loro valori sono gli stessi.

In termini di programmazione, l'identità è solitamente confusa dall'uguaglianza dei riferimenti. Se il puntatore a entrambi i termini è uguale (!), L'oggetto a cui stanno puntando è esattamente lo stesso. Tuttavia, se i puntatori sono diversi, il valore degli oggetti a cui stanno puntando può ancora essere uguale. In C # l'identità può essere verificata utilizzando il Object.ReferenceEqualsmembro statico , mentre l'uguaglianza viene verificata utilizzando il Object.Equalsmembro non statico . Poiché stai eseguendo il casting di due numeri interi sugli oggetti (che è chiamato "boxing", btw), l'operatore ==di objectesegue il primo controllo, che è mappato per impostazione predefinita Object.ReferenceEqualse controlla l'identità. Se chiami esplicitamente il Equalsmembro non statico , l' invio dinamico risulta in una chiamata a Int32.Equals, che verifica l'uguaglianza.

Entrambi i concetti sono simili, ma non uguali. All'inizio possono sembrare confusi, ma la piccola differenza è molto importante! Immagina due persone, ovvero "Alice" e "Bob". Entrambi vivono in una casa gialla. Partendo dal presupposto che Alice e Bob vivano in un quartiere, dove le case differiscono solo nel colore, potrebbero vivere entrambi in case gialle diverse. Se confronti entrambe le case riconoscerai che sono assolutamente uguali, perché sono entrambe gialle! Tuttavia, non condividono la stessa casa e quindi le loro case sono uguali , ma non identiche . L'identità implicherebbe che vivono nella stessa casa.

Nota : alcune lingue stanno definendo l' ===operatore per verificare l'identità.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.