Qual è la differenza tra == e Equals () per le primitive in C #?


180

Considera questo codice:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Entrambi inte shortsono tipi primitivi, ma un confronto con ==restituisce vero e un confronto con Equalsrestituisce falso.

Perché?


9
@OrangeDog Per favore, pensa alla domanda e poi vota per chiudere

4
A ciò manca l'evidente tentativo inverso:Console.WriteLine(age.Equals(newAge));
ANeves

3
Il duplicato non spiega questo comportamento; si tratta solo di ciò che Equals()è in generale.
SLaks

37
Ho risposto a questa esatta domanda sul blog Coverity qualche giorno fa. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos: la specifica utilizza effettivamente il termine "tipi primitivi" due volte senza mai definirlo; l'implicazione è che i tipi primitivi sono tipi di valore incorporati, ma questo non è mai stato chiarito. Ho raccomandato a Mads che il termine sia semplicemente cancellato dalle specifiche in quanto sembra creare più confusione di quanto non rimuova.
Eric Lippert,

Risposte:


262

Risposta breve:

L'uguaglianza è complicata.

Risposta dettagliata:

I tipi di primitive sovrascrivono la base object.Equals(object)e restituiscono true se il riquadro objectè dello stesso tipo e valore. (Si noti che funzionerà anche per i tipi nullable; i tipi nullable non null vengono sempre inseriti in un'istanza del tipo sottostante.)

Poiché newAgeè a short, il suo Equals(object)metodo restituisce true solo se si passa un short box con lo stesso valore. Stai passando un box int, quindi restituisce false.

Al contrario, l' ==operatore è definito come prendere due ints (o shorts o longs).
Quando lo chiami con an inte a short, il compilatore convertirà implicitamente shortin inte confronterà le ints risultanti per valore.

Altri modi per farlo funzionare

I tipi primitivi hanno anche il loro Equals()metodo che accetta lo stesso tipo.
Se scrivi age.Equals(newAge), il compilatore selezionerà int.Equals(int)come il miglior sovraccarico e convertirà implicitamente shortin int. Tornerà quindi true, poiché questo metodo confronta semplicemente le ints direttamente.

shortha anche un short.Equals(short)metodo, ma intnon può essere convertito in shortmodo implicito , quindi non lo stai chiamando.

Potresti forzarlo a chiamare questo metodo con un cast:

Console.WriteLine(newAge.Equals((short)age)); // true

Questo chiamerà short.Equals(short)direttamente, senza boxe. Se ageè maggiore di 32767, genererà un'eccezione di overflow.

Potresti anche chiamare il short.Equals(object)sovraccarico, ma passare esplicitamente un oggetto in scatola in modo che ottenga lo stesso tipo:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Come l'alternativa precedente, questo genererà un overflow se non rientra in a short. A differenza della soluzione precedente, inserirà shortun oggetto, sprecando tempo e memoria.

Codice sorgente:

Ecco entrambi i Equals()metodi dal codice sorgente effettivo:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Ulteriori letture:

Vedi Eric Lippert .


3
@SLaks, se chiamiamo long == int, intimplicitamente convertito in longgiusto?
Selman Genç,

1
E sì, ho scritto tutto questo senza davvero provarlo.
SLaks, il

1
Si ricorda che, nel codice della questione, se si cambia int age = 25;a const int age = 25;, allora il risultato cambierà. Questo perché in quel caso esiste una conversione implicita da inta short. Vedi Conversioni di espressioni costanti implicite .
Jeppe Stig Nielsen,

2
@SLaks sì, ma il testo della tua risposta "il valore passato" può essere interpretato in entrambi i modi (come il valore passato dallo sviluppatore o il valore che viene effettivamente passato dal CLR dopo l'annullamento del box). Immagino che l'utente casuale che non conosce già le risposte qui lo leggerà come il primo
JaredPar

2
@ Rachel: solo che non è vero; l' operatore predefinito == confronta i tipi di riferimento per riferimento. Per i tipi di valore e per i tipi che sovraccaricano ==, non è così.
SLaks

55

Perché non c'è sovraccarico per short.Equalsquello accetta un int. Pertanto, questo si chiama:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objnon è un short.. quindi è falso.


12

Quando passi inta short"Equals" passi object:

inserisci qui la descrizione dell'immagine Quindi viene eseguito questo pseudocodice:

return obj is short && this == (short)obj;


10

==viene utilizzato per verificare una condizione uguale, può essere considerato un operatore (operatore booleano), solo per confrontare 2 cose e qui il tipo di dati non ha importanza in quanto verrebbe eseguito un casting di tipo e Equalsviene anche utilizzato per verificare la condizione uguale , ma in questo caso i tipi di dati dovrebbero essere uguali. N è un metodo non un operatore.

Di seguito è riportato un piccolo esempio preso da quello che hai fornito e questo chiarirà la differenza in breve.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

nell'esempio sopra, X e Y hanno gli stessi valori, vale a dire 1, e quando usiamo ==, restituirà vero, come nel caso di ==, il tipo breve viene convertito in int dal compilatore e il risultato è dato.

e quando usiamo Equals, il confronto è fatto, ma il cast del tipo non è fatto dal compilatore, quindi viene restituito falso.

Ragazzi, per favore fatemi sapere se sbaglio.


6

In molti contesti in cui un argomento di metodo o operatore non è del tipo richiesto, il compilatore C # tenterà di eseguire una conversione di tipo implicita. Se il compilatore può fare in modo che tutti gli argomenti soddisfino i loro operatori e metodi aggiungendo conversioni implicite, lo farà senza lamentarsi, anche se in alcuni casi (specialmente con test di uguaglianza!) I risultati potrebbero essere sorprendenti.

Inoltre, ogni tipo di valore come into shorteffettivamente descrive sia un tipo di valore che un tipo di oggetto (*). Esistono conversioni implicite per convertire i valori in altri tipi di valori e convertire qualsiasi tipo di valore nel tipo di oggetto corrispondente, ma i diversi tipi di oggetti non sono implicitamente convertibili tra loro.

Se si utilizza l' ==operatore per confrontare a shorte an int, il valore shortverrà convertito implicitamente in un int. Se il suo valore numerico era uguale a quello di int, il inta cui è stato convertito sarà uguale inta cui viene confrontato. Se si tenta di utilizzare il Equalsmetodo in breve per confrontarlo con una int, tuttavia, l'unica conversione implicita che soddisferebbe un sovraccarico del Equalsmetodo sarebbe la conversione al tipo di oggetto corrispondente int. Quando shortviene chiesto se corrisponde all'oggetto passato, osserverà che l'oggetto in questione è intpiuttosto che a shorte quindi conclude che non può essere uguale.

In generale, sebbene il compilatore non se ne lamenti, si dovrebbe evitare di confrontare cose che non sono dello stesso tipo; se uno è interessato al fatto che la conversione di cose in una forma comune darebbe lo stesso risultato, si dovrebbe eseguire esplicitamente tale conversione. Considera, ad esempio,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Ci sono tre modi in cui si potrebbe voler confrontare un inta float. Uno potrebbe voler sapere:

  1. Il floatvalore più vicino possibile alla intcorrispondenza è float?
  2. Il numero intero parte della floatpartita è il int?
  3. Esegui inte floatrappresentano lo stesso valore numerico.

Se si tenta di confrontare un inte floatdirettamente, il codice compilato risponderà alla prima domanda; se è quello che intendeva il programmatore, tuttavia, sarà tutt'altro che ovvio. Modificando il confronto in (float)i == fmodo da chiarire che era previsto il primo significato, o (double)i == (double)ffarebbe sì che il codice rispondesse alla terza domanda (e chiarire che era quello che era previsto).

(*) Anche se le specifiche C # considerano un valore di tipo System.Int32come ad esempio un oggetto di tipo System.Int32, tale vista è contraddetta dal requisito che un codice venga eseguito su una piattaforma le cui specifiche considerano valori e oggetti come abitanti di universi diversi. Inoltre, se Tè un tipo di riferimento ed xè un T, allora Tdovrebbe essere possibile fare riferimento a un riferimento di tipo x. Pertanto, se una variabile vdi tipo Int32contiene un Object, un riferimento di tipo Objectdovrebbe essere in grado di contenere un riferimento vo il suo contenuto. In effetti, un riferimento di tipo Objectsarebbe in grado di indicare un oggetto contenente dati copiati v, ma non a vse stesso né al suo contenuto. Ciò suggerirebbe che nessuno dei duevné il suo contenuto è davvero un Object.


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intSbagliato. A differenza di Java, C # non ha tipi primitivi e in box separati. Viene inscatolato objectperché è l'unico altro sovraccarico di Equals().
SLaks

La prima e la terza domanda sono identiche; il valore esatto era già perso al momento della conversione in float. Lanciare un floata doublenon creerà magicamente una nuova precisione.
SLaks

@SLaks: secondo le specifiche ECMA, che descrivono la macchina virtuale su cui gira C #, ogni definizione del tipo di valore crea due tipi distinti. Le specifiche C # potrebbero dire che il contenuto di una posizione di archiviazione di tipo List<String>.Enumeratore un oggetto heap di tipo List<String>.Enumeratorsono gli stessi, ma le specifiche ECMA / CLI dicono che sono diverse e anche se usate in C # si comportano diversamente.
supercat

@SLaks: se ie fciascuno fosse convertito doubleprima del confronto, produrrebbe 16777217.0 e 16777216.0, che si confrontano come disuguali. La conversione i floatprodurrebbe 16777216.0f, rispetto a f.
supercat

@SLaks: per un semplice esempio della differenza tra tipi di posizione di archiviazione e tipi di oggetti in scatola, considerare il metodo bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Il tipo di oggetto inscatolato corrispondente a un tipo di valore può soddisfare il tipo di parametro ReferenceEqualstramite un'identità che preserva l' upcast; il tipo di posizione di archiviazione, tuttavia, richiede una conversione che non preserva l'identità . Se lanciare a Tper Ufornisce un riferimento a qualcosa di diverso dall'originale T, ciò mi suggerirebbe che a Tnon è davvero a U.
supercat

5

Equals () è un metodo di System.Object
Sintassi della classe : Bool virtuale pubblico Equals ()
Raccomandazione se vogliamo confrontare lo stato di due oggetti, allora dovremmo usare il metodo Equals ()

come indicato sopra, le risposte == gli operatori confrontano i valori sono gli stessi.

Non confonderti con ReferenceEqual

Reference Equals ()
Sintassi: public static bool ReferenceEquals ()
Determina se l'istanza di oggetti specificata è della stessa istanza


8
Questo non risponde affatto alla domanda.
SLaks

SLak non ho spiegato con esempi che sono alla base della domanda sopra.
Sugat Mankar,

4

Quello che devi capire è che fare ==finirà sempre per chiamare un metodo. La domanda è se chiamare ==e Equalsfinisce per chiamare / fare le stesse cose.

Con i tipi di riferimento, ==1 ° verificherà sempre se i riferimenti sono uguali ( Object.ReferenceEquals). Equalsd'altra parte può essere ignorato e può verificare se alcuni valori sono uguali.

EDIT: per rispondere a svick e aggiungere il commento di SLaks, ecco un po 'di codice IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.

Quindi quale metodo confronta due ints con == chiama? Suggerimento: non esiste un operator ==metodo per Int32, ma ce n'è uno perString .
svick,

2
Questo non risponde affatto alla domanda.
SLaks

@SLaks: in effetti non risponde alla domanda specifica su int e confronto breve, hai già risposto. Sento ancora interessante spiegare che ==non si limita a fare magie, ma alla fine chiama semplicemente un metodo (la maggior parte dei programmatori probabilmente non ha mai implementato / ignorato alcun operatore). Forse avrei potuto aggiungere un commento alla tua domanda invece di aggiungere la mia risposta. Sentiti libero di aggiornare il tuo se ritieni che ciò che ho detto sia rilevante.
user276648

Si noti che ==sui tipi primitivi non è un operatore sovraccarico, ma una caratteristica del linguaggio intrinseco che viene compilata per l' ceqistruzione IL.
SLaks,

3

== In Primitivo

Console.WriteLine(age == newAge);          // true

Nel confronto primitivo == l'operatore si comporta abbastanza ovvio, in C # ci sono molti == sovraccarico dell'operatore disponibili.

  • string == string
  • int == int
  • uint == uint
  • long == long
  • molti altri

Quindi in questo caso non è possibile alcuna conversione implicita da inta shortma shorta int. Quindi newAge viene convertito in int e si verifica un confronto che restituisce true poiché entrambi hanno lo stesso valore. Quindi è equivalente a:

Console.WriteLine(age == (int)newAge);          // true

.Equals () in Primitivo

Console.WriteLine(newAge.Equals(age));         //false

Qui dobbiamo vedere cos'è il metodo Equals (), che chiamiamo Equals con una variabile di tipo breve. Quindi ci sono tre possibilità:

  • Uguale a (oggetto, oggetto) // metodo statico dall'oggetto
  • Uguale a (oggetto) // metodo virtuale dall'oggetto
  • Equals (short) // Implementa IEquatable.Equals (short)

Il primo tipo non è un caso qui poiché il numero di argomenti è diverso che chiamiamo con un solo argomento di tipo int. Il terzo è anche eliminato, come indicato sopra, non è possibile la conversione implicita di int in short. Quindi qui Equals(object)viene chiamato il secondo tipo di . Il short.Equals(object)è:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Quindi qui la condizione è stata testata, il z is shortche è falso poiché z è un int quindi restituisce falso.

Ecco un articolo dettagliato di Eric Lippert

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.