Operatore C # .Equals (), .ReferenceEquals () e ==


85

La mia comprensione di questi tre era:

  • .Equals()test per l'uguaglianza dei dati (per la mancanza di una descrizione migliore). .Equals()può restituire True per istanze diverse dello stesso oggetto e questo è il metodo più comunemente ignorato.

  • .ReferenceEquals() verifica se due oggetti sono o meno la stessa istanza e non possono essere sovrascritti.

  • ==è uguale a quello ReferenceEquals()predefinito, ma può essere sovrascritto.

Ma la stazione C # afferma:

Nella classe oggetto, i metodi Equalse ReferenceEqualssono semanticamente equivalenti, tranne per il fatto che ReferenceEqualsfunziona solo sulle istanze dell'oggetto. Il ReferenceEqualsmetodo è statico.

Adesso non capisco. Qualcuno può far luce su questo?



Vedi stackoverflow.com/questions/814878/… e molte altre domande StackOverflow su questo argomento.
Ian Mercer

@High ho. È solo la parte che ho estratto da C # Station che mi confonde.
999999

Risposte:


87

La fonte della tua confusione sembra essere che c'è un errore di battitura nell'estratto dalla stazione C #, che dovrebbe leggere: "... tranne che Equals funziona solo su istanze di oggetti. Il metodo ReferenceEquals è statico."


Hai vagamente ragione circa le differenze nei significati semantici di ciascuno (sebbene "istanze diverse dello stesso oggetto" sembri un po 'confuso, probabilmente dovrebbe leggere "istanze diverse dello stesso tipo ) e su quali possono essere sovrascritte.

Se lo lasciamo da parte, affrontiamo l'ultima parte della tua domanda, ovvero come funzionano con System.Objectistanze e System.Objectriferimenti semplici (abbiamo bisogno di entrambi per schivare la natura non polimorfica di ==). Qui, tutte e tre le operazioni funzioneranno in modo equivalente , ma con un avvertimento: Equalsnon possono essere invocate su null.

Equalsè un metodo di istanza che accetta un parametro (che può essere null). Poiché si tratta di un metodo di istanza (deve essere richiamato su un oggetto reale), non può essere richiamato su un null-reference.

ReferenceEqualsè un metodo statico che accetta due parametri, uno dei quali / entrambi possono essere null. Poiché è statico (non associato a un'istanza di un oggetto ), non genererà NullReferenceExceptionin nessuna circostanza.

==è un operatore, che, in questo caso ( object), si comporta in modo identico a ReferenceEquals. Non lancerà neanche un NullReferenceException.

Illustrare:

object o1 = null;
object o2 = new object();

//Technically, these should read object.ReferenceEquals for clarity, but this is redundant.
ReferenceEquals(o1, o1); //true
ReferenceEquals(o1, o2); //false
ReferenceEquals(o2, o1); //false
ReferenceEquals(o2, o2); //true

o1.Equals(o1); //NullReferenceException
o1.Equals(o2); //NullReferenceException
o2.Equals(o1); //false
o2.Equals(o2); //true

Quindi l'estratto dalla stazione C # citato sopra è sbagliato (specialmente se sovrascrivo .Equals())?
999999

1
L'estratto afferma "in objectclasse" . Penso che tu abbia saltato quella parte? Perché altrimenti non parleresti di sovrascriverlo.
Domenic

1
La mia risposta riguarda solo la objectclasse.
Ani

@Ani: la tua frase di seguito era errata, il metodo statico può generare NullReferenceException: poiché è statico (non associato a un'istanza di oggetto), non genererà un'eccezioneNullReferenceException in nessuna circostanza.
selvaraj

2
Equalsè anche un metodo statico su objectcui accetta due parametri. Uno o entrambi possono quindi essere null.
Weston

20

Dai un'occhiata a questo articolo di MSDN sull'argomento.

Penso che i punti pertinenti siano:

Per verificare l'uguaglianza dei riferimenti, utilizzare ReferenceEquals. Per verificare l'uguaglianza dei valori, utilizzare Equals o Equals.

Per impostazione predefinita, l'operatore == verifica l'uguaglianza dei riferimenti determinando se due riferimenti indicano lo stesso oggetto, quindi i tipi di riferimento non devono implementare l'operatore == per ottenere questa funzionalità. Quando un tipo è immutabile, il che significa che i dati contenuti nell'istanza non possono essere modificati, l'operatore di sovraccarico == per confrontare l'uguaglianza dei valori invece dell'uguaglianza dei riferimenti può essere utile perché, come oggetti immutabili, possono essere considerati gli stessi fintanto che hanno il stesso valore.

Spero che sia di aiuto!


6
sfortunatamente, il collegamento è morto. +1 per copiare le informazioni pertinenti.
Pac0

7

La tua comprensione di .ReferenceEquals è corretta.

.Equals controlla l'uguaglianza dei dati per i tipi di valore e l'uguaglianza di riferimento per i tipi non-valore (oggetti generali).

.Equals può essere sovrascritto per gli oggetti per eseguire una qualche forma di controllo dell'uguaglianza dei dati

EDIT: Inoltre, .ReferenceEquals non può essere utilizzato sui tipi di valore (beh, può, ma sarà sempre falso)


3

Voglio aggiungere i miei cinque centesimi sul confronto con "null".

  1. ReferenceEquals (object, object) è uguale a "(object) arg1 == arg2" (quindi in caso di tipi di valore, ottieni boxing e ci vuole tempo). Ma questo metodo è l'unico modo sicuro al 100% per controllare il tuo argomento per null in diverse situazioni, come

    • a) prima di chiamare i suoi membri tramite. operatore
    • b) verifica del risultato dell'AS operatore.
  2. == e uguale a (). Perché sto dicendo che ReferenceEquals è sicuro al 100% con controlli null? Immagina di scrivere estensioni generiche nelle librerie principali di più progetti e di limitare il tipo di parametro generico a qualche tipo di dominio. Questo tipo può introdurre l'operatore "==" - ora o più tardi (e credetemi, ho visto molto, questo operatore può avere una logica molto "strana", specialmente se si tratta di oggetti di dominio o di persistenza). Si prova a controllare il proprio argomento per null e quindi si chiama l'operazione del membro su di esso. Sorpresa, puoi avere NullRef qui. Perché l'operatore == è quasi uguale a Equals () - molto personalizzato e molto imprevedibile. C'è una differenza, tuttavia, che dovrebbe essere presa in considerazione: se non limiti il ​​tuo parametro generico a qualche tipo personalizzato (== può essere utilizzato solo se il tuo tipo è "class"), l'operatore == è lo stesso dell'oggetto . ReferenceEquals (..). L'implementazione uguale viene sempre utilizzata dal tipo finale, poiché è virtuale.

Quindi la mia raccomandazione è che, quando scrivi i tuoi tipi o derivi da tipi noti, puoi usare == per verificare la presenza di null. Altrimenti usa object.ReferenceEquals (arg, null).


1

Nella classe Object .Equals implementa l'identità, non l'uguaglianza. Controlla se i riferimenti sono uguali. Il codice potrebbe essere così:

public virtual Boolean Equals(Object other) {
    if (this == other) return true;
    return false;
}

Durante l'implementazione di .Equals nella tua classe dovresti chiamare la classe base .Equals solo se la classe base non è Object. Sì, è complicato.

Inoltre, poiché le classi derivate possono sovrascrivere .Equals e quindi non è possibile chiamarlo per verificare l'identità, Microsoft ha aggiunto il metodo statico .ReferenceEquals.

Se utilizzi una classe, logicamente .Equals verifica l'uguaglianza e .ReferenceEquals verifica l'identità.


1

Ho ampliato l'eccellente risposta di Ani per mostrare le differenze chiave quando si tratta di tipi di riferimento e metodi di uguaglianza sostituiti.

  • Puoi vedere una versione funzionante di questo codice qui: https://dotnetfiddle.net/dFKMhB
  • In alternativa, questo codice può essere incollato su LinqPad ed eseguito come Language: C# Program.

.

void Main()
{

    //odd os are null; evens are not null
    object o1 = null;
    object o2 = new object();
    object o3 = null;
    object o4 = new object();
    object o5 = o1;
    object o6 = o2;

    Demo d1 = new Demo(Guid.Empty);
    Demo d2 = new Demo(Guid.NewGuid());
    Demo d3 = new Demo(Guid.Empty);

    Debug.WriteLine("comparing null with null always yields true...");
    ShowResult("ReferenceEquals(o1, o1)", () => ReferenceEquals(o1, o1)); //true
    ShowResult("ReferenceEquals(o3, o1)", () => ReferenceEquals(o3, o1)); //true
    ShowResult("ReferenceEquals(o5, o1)", () => ReferenceEquals(o5, o1)); //true 
    ShowResult("o1 == o1", () => o1 == o1); //true
    ShowResult("o3 == o1", () => o3 == o1); //true
    ShowResult("o5 == o1", () => o5 == o1); //true 

    Debug.WriteLine("...though because the object's null, we can't call methods on the object (i.e. we'd get a null reference exception).");
    ShowResult("o1.Equals(o1)", () => o1.Equals(o1)); //NullReferenceException
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o3.Equals(o1)", () => o3.Equals(o1)); //NullReferenceException
    ShowResult("o3.Equals(o2)", () => o3.Equals(o2)); //NullReferenceException
    ShowResult("o5.Equals(o1)", () => o5.Equals(o1));  //NullReferenceException
    ShowResult("o5.Equals(o2)", () => o5.Equals(o1));  //NullReferenceException

    Debug.WriteLine("Comparing a null object with a non null object always yeilds false");
    ShowResult("ReferenceEquals(o1, o2)", () => ReferenceEquals(o1, o2)); //false
    ShowResult("ReferenceEquals(o2, o1)", () => ReferenceEquals(o2, o1)); //false
    ShowResult("ReferenceEquals(o3, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o4, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("ReferenceEquals(o5, o2)", () => ReferenceEquals(o3, o2)); //false
    ShowResult("ReferenceEquals(o6, o1)", () => ReferenceEquals(o4, o1)); //false
    ShowResult("o1 == o2)", () => o1 == o2); //false
    ShowResult("o2 == o1)", () => o2 == o1); //false
    ShowResult("o3 == o2)", () => o3 == o2); //false
    ShowResult("o4 == o1)", () => o4 == o1); //false
    ShowResult("o5 == o2)", () => o3 == o2); //false
    ShowResult("o6 == o1)", () => o4 == o1); //false
    ShowResult("o2.Equals(o1)", () => o2.Equals(o1)); //false
    ShowResult("o4.Equals(o1)", () => o4.Equals(o1)); //false
    ShowResult("o6.Equals(o1)", () => o4.Equals(o1)); //false

    Debug.WriteLine("(though again, we can't call methods on a null object:");
    ShowResult("o1.Equals(o2)", () => o1.Equals(o2)); //NullReferenceException
    ShowResult("o1.Equals(o4)", () => o1.Equals(o4)); //NullReferenceException
    ShowResult("o1.Equals(o6)", () => o1.Equals(o6)); //NullReferenceException

    Debug.WriteLine("Comparing 2 references to the same object always yields true");
    ShowResult("ReferenceEquals(o2, o2)", () => ReferenceEquals(o2, o2)); //true    
    ShowResult("ReferenceEquals(o6, o2)", () => ReferenceEquals(o6, o2)); //true <-- Interesting
    ShowResult("o2 == o2", () => o2 == o2); //true  
    ShowResult("o6 == o2", () => o6 == o2); //true <-- Interesting
    ShowResult("o2.Equals(o2)", () => o2.Equals(o2)); //true 
    ShowResult("o6.Equals(o2)", () => o6.Equals(o2)); //true <-- Interesting

    Debug.WriteLine("However, comparing 2 objects may yield false even if those objects have the same values, if those objects reside in different address spaces (i.e. they're references to different objects, even if the values are similar)");
    Debug.WriteLine("NB: This is an important difference between Reference Types and Value Types.");
    ShowResult("ReferenceEquals(o4, o2)", () => ReferenceEquals(o4, o2)); //false <-- Interesting
    ShowResult("o4 == o2", () => o4 == o2); //false <-- Interesting
    ShowResult("o4.Equals(o2)", () => o4.Equals(o2)); //false <-- Interesting

    Debug.WriteLine("We can override the object's equality operator though, in which case we define what's considered equal");
    Debug.WriteLine("e.g. these objects have different ids, so we treat as not equal");
    ShowResult("ReferenceEquals(d1,d2)",()=>ReferenceEquals(d1,d2)); //false
    ShowResult("ReferenceEquals(d2,d1)",()=>ReferenceEquals(d2,d1)); //false
    ShowResult("d1 == d2",()=>d1 == d2); //false
    ShowResult("d2 == d1",()=>d2 == d1); //false
    ShowResult("d1.Equals(d2)",()=>d1.Equals(d2)); //false
    ShowResult("d2.Equals(d1)",()=>d2.Equals(d1)); //false
    Debug.WriteLine("...whilst these are different objects with the same id; so we treat as equal when using the overridden Equals method...");
    ShowResult("d1.Equals(d3)",()=>d1.Equals(d3)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    ShowResult("d3.Equals(d1)",()=>d3.Equals(d1)); //true <-- Interesting (sort of; different to what we saw in comparing o2 with o6; but is just running the code we wrote as we'd expect)
    Debug.WriteLine("...but as different when using the other equality tests.");
    ShowResult("ReferenceEquals(d1,d3)",()=>ReferenceEquals(d1,d3)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("ReferenceEquals(d3,d1)",()=>ReferenceEquals(d3,d1)); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d1 == d3",()=>d1 == d3); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)
    ShowResult("d3 == d1",()=>d3 == d1); //false <-- Interesting (sort of; same result we had comparing o2 with o6; but shows that ReferenceEquals does not use the overridden Equals method)


    Debug.WriteLine("For completeness, here's an example of overriding the == operator (wihtout overriding the Equals method; though in reality if overriding == you'd probably want to override Equals too).");
    Demo2 d2a = new Demo2(Guid.Empty);
    Demo2 d2b = new Demo2(Guid.NewGuid());
    Demo2 d2c = new Demo2(Guid.Empty);
    ShowResult("d2a == d2a", () => d2a == d2a); //true
    ShowResult("d2b == d2a", () => d2b == d2a); //false
    ShowResult("d2c == d2a", () => d2c == d2a); //true <-- interesting
    ShowResult("d2a != d2a", () => d2a != d2a); //false
    ShowResult("d2b != d2a", () => d2b != d2a); //true
    ShowResult("d2c != d2a", () => d2c != d2a); //false <-- interesting
    ShowResult("ReferenceEquals(d2a,d2a)", () => ReferenceEquals(d2a, d2a)); //true
    ShowResult("ReferenceEquals(d2b,d2a)", () => ReferenceEquals(d2b, d2a)); //false
    ShowResult("ReferenceEquals(d2c,d2a)", () => ReferenceEquals(d2c, d2a)); //false <-- interesting
    ShowResult("d2a.Equals(d2a)", () => d2a.Equals(d2a)); //true
    ShowResult("d2b.Equals(d2a)", () => d2b.Equals(d2a)); //false
    ShowResult("d2c.Equals(d2a)", () => d2c.Equals(d2a)); //false <-- interesting   

}



//this code's just used to help show the output in a friendly manner
public delegate bool Statement();
void ShowResult(string statementText, Statement statement)
{
    try 
    {
        Debug.WriteLine("\t{0} => {1}",statementText, statement());
    }
    catch(Exception e)
    {
        Debug.WriteLine("\t{0} => throws {1}",statementText, e.GetType());
    }
}

class Demo
{
    Guid id;
    public Demo(Guid id) { this.id = id; }
    public override bool Equals(object obj)
    {
        return Equals(obj as Demo); //if objects are of non-comparable types, obj will be converted to null
    }
    public bool Equals(Demo obj)
    {
        if (obj == null)
        {
            return false;
        }
        else
        {
            return id.Equals(obj.id);
        }
    }
    //if two objects are Equal their hashcodes must be equal
    //however, if two objects hash codes are equal it is not necessarily true that the objects are equal
    //i.e. equal objects are a subset of equal hashcodes
    //more info here: https://stackoverflow.com/a/371348/361842
    public override int GetHashCode()
    {
        return id.GetHashCode();
    }
}

class Demo2
{
    Guid id;
    public Demo2(Guid id)
    {
        this.id = id;
    }

    public static bool operator ==(Demo2 obj1, Demo2 obj2)
    {
        if (ReferenceEquals(null, obj1)) 
        {
            return ReferenceEquals(null, obj2); //true if both are null; false if only obj1 is null
        }
        else
        {
            if(ReferenceEquals(null, obj2)) 
            {
                return false; //obj1 is not null, obj2 is; therefore false
            }
            else
            {
                return obj1.id == obj2.id; //return true if IDs are the same; else return false
            }
        }
    }

    // NB: We also HAVE to override this as below if overriding the == operator; this is enforced by the compiler.  However, oddly we could choose to override it different to the below; but typically that would be a bad idea...
    public static bool operator !=(Demo2 obj1, Demo2 obj2)
    {
        return !(obj1 == obj2);
    }
}

-3

Equals()verifica il codice hash o l'equivalenza a seconda del tipo sottostante (valore / riferimento) ed ReferenceEquals()è destinato a controllare sempre il codice hash. ReferenceEqualsrestituisce truese entrambi gli oggetti puntano alla stessa posizione di memoria.

double e = 1.5;
double d = e;
object o1 = d;
object o2 = d;

Console.WriteLine(o1.Equals(o2)); // True
Console.WriteLine(Object.Equals(o1, o2)); // True
Console.WriteLine(Object.ReferenceEquals(o1, o2)); // False

Console.WriteLine(e.Equals(d)); // True
Console.WriteLine(Object.Equals(e, d)); // True
Console.WriteLine(Object.ReferenceEquals(e, d)); // False

3
Questo non ha senso. Né Equals né ReferenceEquals esaminano l'HashCode. C'è semplicemente un requisito per cui gli oggetti HashCodes of Equals devono essere uguali. E gli oggetti non puntano da nessuna parte ... ReferenceEquals è vero se e solo se entrambi i suoi argomenti sono lo stesso oggetto di riferimento o entrambi sono nulli.
Jim Balter
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.