C # .Equals (), .ReferenceEquals () und == Operator

82

Mein Verständnis dieser drei war:

  • .Equals()Tests zur Datengleichheit (mangels einer besseren Beschreibung). .Equals()kann True für verschiedene Instanzen desselben Objekts zurückgeben, und dies ist die am häufigsten überschriebene Methode.

  • .ReferenceEquals() Testet, ob zwei Objekte dieselbe Instanz sind und nicht überschrieben werden können.

  • ==ist die gleiche wie die ReferenceEquals()Standardeinstellung, kann jedoch überschrieben werden.

Aber C # Station sagt:

In der Objektklasse sind die Methoden Equalsund ReferenceEqualssemantisch äquivalent, außer dass die Methoden ReferenceEqualsnur für Objektinstanzen funktionieren. Die ReferenceEqualsMethode ist statisch.

Jetzt verstehe ich es nicht. Kann jemand etwas Licht ins Dunkel bringen?

999999
quelle
Siehe stackoverflow.com/questions/814878/… und viele andere StackOverflow-Fragen zu diesem Thema.
Ian Mercer
@Hoch habe ich. Es ist nur der Teil, den ich aus C # Station extrahiert habe, der mich verwirrt.
999999

Antworten:

86

Die Ursache Ihrer Verwirrung scheint zu sein, dass der Auszug aus der C # -Station einen Tippfehler enthält, der lauten sollte: "... außer dass Equals nur für Objektinstanzen funktioniert. Die ReferenceEquals-Methode ist statisch."


Sie haben lose Recht mit den Unterschieden in den semantischen Bedeutungen der einzelnen (obwohl "verschiedene Instanzen desselben Objekts" ein wenig verwirrt erscheinen, sollte es wahrscheinlich "verschiedene Instanzen desselben Typs " lauten ) und über die überschrieben werden kann.

Wenn wir das beiseite lassen, wollen wir uns mit dem letzten Teil Ihrer Frage befassen, dh wie sie mit einfachen System.ObjectInstanzen und System.ObjectReferenzen funktionieren (wir müssen beide der nicht-polymorphen Natur von ausweichen ==). Hier funktionieren alle drei Operationen gleich , jedoch mit einer Einschränkung: EqualsKann nicht aufgerufen werden null.

Equalsist ein Beispiel , das Verfahren nimmt einen Parameter (die kann sein null). Da es sich um eine Instanzmethode handelt (die für ein tatsächliches Objekt aufgerufen werden muss), kann sie nicht für eine nullReferenz aufgerufen werden .

ReferenceEqualsist eine statische Methode, die zwei Parameter verwendet, von denen einer / beide sein können null. Da sie statisch ist (nicht mit einem Objekt verbunden Beispiel ), wird es nicht ein Wurf NullReferenceExceptionunter keinen Umständen.

==ist ein Operator, der sich in diesem Fall ( object) identisch verhält ReferenceEquals. Es wird auch kein werfen NullReferenceException.

Um zu zeigen:

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
Ani
quelle
Ist der oben zitierte Auszug aus der C # -Station also falsch (insbesondere, wenn ich ihn überschreibe .Equals())?
999999
1
Der Auszug besagt "in der objectKlasse" . Ich denke, Sie haben diesen Teil übersprungen? Denn sonst würden Sie nicht darüber reden, es zu überschreiben.
Domenic
1
Meine Antwort bezieht sich nur auf die objectKlasse.
Ani
@Ani: Ihr unten stehender Satz war falsch. Eine statische Methode kann eine NullReferenceException auslösen. Da sie statisch ist (keiner Objektinstanz zugeordnet), wird unter keinen Umständen eine NullReferenceException ausgelöst.
Selvaraj
2
Equalsist auch eine statische Methode, bei objectder zwei Parameter verwendet werden. Einer oder beide können dann sein null.
Weston
19

Schauen Sie sich diesen MSDN-Artikel zu diesem Thema an.

Ich denke, die relevanten Punkte sind:

Verwenden Sie ReferenceEquals, um die Referenzgleichheit zu überprüfen. Verwenden Sie Gleich oder Gleich, um die Wertgleichheit zu überprüfen.

Standardmäßig prüft der Operator == die Referenzgleichheit, indem er feststellt, ob zwei Referenzen dasselbe Objekt angeben. Daher müssen Referenztypen den Operator == nicht implementieren, um diese Funktionalität zu erhalten. Wenn ein Typ unveränderlich ist, was bedeutet, dass die in der Instanz enthaltenen Daten nicht geändert werden können, kann das Überladen des Operators == zum Vergleichen der Wertgleichheit anstelle der Referenzgleichheit nützlich sein, da sie als unveränderliche Objekte als gleich angesehen werden können, solange sie die haben gleicher Wert.

Hoffe das hilft!

Alastair Pitts
quelle
6
Link ist leider tot. +1 zum Kopieren der relevanten Informationen.
Pac0
6

Ihr Verständnis von .ReferenceEquals ist korrekt.

.Equals überprüft die Datengleichheit für Werttypen und die Referenzgleichheit für Nichtwerttypen (allgemeine Objekte).

Gleichheiten können für Objekte überschrieben werden, um eine Form der Datengleichheitsprüfung durchzuführen

EDIT: Außerdem kann .ReferenceEquals nicht für Werttypen verwendet werden (nun, es kann, wird aber immer falsch sein).

Luke Schafer
quelle
3

Ich möchte meine fünf Cent zum Vergleich mit "null" hinzufügen.

  1. ReferenceEquals (Objekt, Objekt) ist dasselbe wie "(Objekt) arg1 == arg2" (bei Werttypen erhalten Sie also Boxen und es braucht Zeit). Diese Methode ist jedoch die einzige 100% sichere Methode, um Ihr Argument in verschiedenen Situationen auf Null zu überprüfen, z

    • a) bevor Sie die Mitglieder über anrufen. Operator
    • b) Überprüfen des Ergebnisses des AS-Operators.
  2. == und Equals (). Warum sage ich, dass ReferenceEquals bei Nullprüfungen 100% sicher ist? Stellen Sie sich vor, Sie schreiben generische Erweiterungen in projektübergreifende Kernbibliotheken und beschränken den generischen Parametertyp auf einen bestimmten Domänentyp. Dieser Typ kann den Operator "==" einführen - jetzt oder später (und glauben Sie mir, ich habe viel gesehen, dieser Operator kann eine sehr "seltsame" Logik haben, insbesondere wenn es um Domänen- oder Persistenzobjekte geht). Sie versuchen, Ihr Argument auf null zu überprüfen und rufen dann die Member-Operation auf. Überraschung, Sie können hier NullRef haben. Weil der Operator == fast mit Equals () identisch ist - sehr benutzerdefiniert und sehr unvorhersehbar. Es gibt jedoch einen Unterschied, der berücksichtigt werden sollte: Wenn Sie Ihren generischen Parameter nicht auf einen benutzerdefinierten Typ beschränken (== kann nur verwendet werden, wenn Ihr Typ "Klasse" ist), ist der Operator == derselbe wie das Objekt . ReferenceEquals (..). Die Implementierung von Equals wird immer vom endgültigen Typ verwendet, da sie virtuell ist.

Meine Empfehlung lautet also, wenn Sie Ihre eigenen Typen schreiben oder von bekannten Typen ableiten, können Sie == verwenden, um nach Null zu suchen. Verwenden Sie andernfalls object.ReferenceEquals (arg, null).

sotonika
quelle
1

In der Objektklasse .Equals implementiert Identität, nicht Gleichheit. Es wird geprüft, ob die Referenzen gleich sind. Der Code könnte folgendermaßen aussehen:

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

Während der Implementierung von .Equals in Ihrer Klasse sollten Sie die Basisklasse .Equals nur aufrufen, wenn die Basisklasse nicht Object ist. Ja, das ist kompliziert.

Darüber hinaus können abgeleitete Klassen .Equals überschreiben, sodass Sie sie nicht aufrufen können, um die Identität zu überprüfen. Microsoft hat die statische .ReferenceEquals-Methode hinzugefügt.

Wenn Sie eine Klasse verwenden, werden für Sie logisch .Equals auf Gleichheit und .ReferenceEquals auf Identität geprüft.

Yola
quelle
1

Ich habe Anis hervorragende Antwort erweitert , um die wichtigsten Unterschiede beim Umgang mit Referenztypen und überschriebenen Gleichheitsmethoden aufzuzeigen.

.

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);
    }
}
JohnLBevan
quelle
-3

Equals()prüft je nach zugrunde liegendem Typ (Wert / Referenz) auf Hash-Code oder Äquivalenz und ReferenceEquals()soll immer nach Hash-Code suchen. ReferenceEqualsGibt zurück, truewenn beide Objekte auf denselben Speicherort zeigen.

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
Atulya
quelle
3
Das ist schwachsinn. Weder Equals noch ReferenceEquals betrachten den HashCode. Es gibt lediglich eine Anforderung, dass HashCodes of Equals-Objekte gleich sein müssen. Und Objekte zeigen nirgendwo hin ... ReferenceEquals ist genau dann wahr, wenn beide Argumente dasselbe Referenzobjekt oder beide null sind.
Jim Balter