Warum unterscheidet sich (Objekt) 0 == (Objekt) 0 von ((Objekt) 0) .Gleich ((Objekt) 0)?

117

Warum unterscheiden sich die folgenden Ausdrücke?

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

Eigentlich kann ich [1] völlig verstehen, da wahrscheinlich die .NET-Laufzeit boxdie Ganzzahl ist und stattdessen die Referenzen vergleicht. Aber warum ist [2] anders?

André Pena
quelle
36
OK, jetzt, da Sie die Antwort auf diese Frage verstanden haben, überprüfen Sie Ihr Verständnis, indem Sie das Ergebnis vorhersagen: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Überprüfen Sie es jetzt anhand der Realität. War Ihre Vorhersage richtig? Wenn nicht, können Sie die Diskrepanz erklären?
Eric Lippert
1
@Star, für empfohlene Lektüre siehe msdn.microsoft.com/en-us/library/vstudio/… für die verfügbaren Überladungen der int16aka shortEquals-Methode, dann siehe msdn.microsoft.com/en-us/library/ms173105.aspx . Ich möchte Eric Lipperts Rätsel nicht verderben, aber es sollte ziemlich einfach sein, es herauszufinden, wenn Sie diese Seiten gelesen haben.
Sam Skuce
2
Ich dachte, das ist eine Java-Frage. Zumindest bevor Sie das 'E' in Equals sehen.
Seteropere
4
@seteropere Java ist eigentlich anders: Das Autoboxing in Java speichert Objekte zwischen, sodass ((Integer)0)==((Integer)0)es als wahr ausgewertet wird.
Jules
1
Sie können es auch versuchen IFormattable x = 0; bool test = (object)x == (object)x;. Es wird kein neues Boxen durchgeführt, wenn sich die Struktur bereits in einer Box befindet.
Jeppe Stig Nielsen

Antworten:

151

Der Grund, warum sich die Aufrufe unterschiedlich verhalten, ist, dass sie an sehr unterschiedliche Methoden gebunden sind.

Der ==Fall wird an den Gleichheitsoperator für statische Referenzen gebunden. Es werden 2 unabhängige Boxwerte interstellt, daher handelt es sich nicht um dieselbe Referenz.

Im zweiten Fall binden Sie an die Instanzmethode Object.Equals. Dies ist eine virtuelle Methode, die nach unten filtert Int32.Equalsund nach einer Ganzzahl mit Kästchen sucht. Beide ganzzahligen Werte sind 0, daher sind sie gleich

JaredPar
quelle
Der ==Fall ruft nicht an Object.ReferenceEquals. Es wird einfach der ceqIL-Befehl erzeugt, um einen Referenzvergleich durchzuführen.
Sam Harwell
8
@ 280Z28 ist das nicht nur, weil der Compiler es inline?
Markmnl
@ 280Z28 Also? Ein ähnlicher Fall ist, dass ihre Boolean.ToString-Methode anscheinend fest codierte Zeichenfolgen in ihrer Funktion enthält, anstatt die öffentlich verfügbar gemachten Boolean.TrueString und Boolean.FalseString zurückzugeben. Es ist irrelevant; Der Punkt ist, ==macht das gleiche wie ReferenceEquals(auf Objekt jedenfalls). Es ist alles nur eine interne Optimierung auf MS-Seite, um unnötige interne Funktionsaufrufe für häufig verwendete Funktionen zu vermeiden.
Nyerguds
6
In der C # -Sprachenspezifikation, Absatz 7.10.6, heißt es: Die vordefinierten Gleichheitsoperatoren für Referenztypen sind: bool operator ==(object x, object y); bool operator !=(object x, object y);Die Operatoren geben das Ergebnis des Vergleichs der beiden Referenzen auf Gleichheit oder Nichtgleichheit zurück. Es ist nicht erforderlich, dass die Methode System.Object.ReferenceEqualszur Bestimmung des Ergebnisses verwendet wird. Zu @markmnl: Nein, der C # -Compiler inline nicht, das ist etwas, was der Jitter manchmal tut (aber in diesem Fall nicht). 280Z28 ist also richtig, die ReferenceEqualsMethode wird eigentlich nicht verwendet.
Jeppe Stig Nielsen
@JaredPar: Es ist interessant, dass die Spezifikation dies sagt, da sich die Sprache nicht so verhält. Bei den oben definierten Operatoren und Variablen Cat Whiskers; Dog Fido; IDog Fred;(für nicht verwandte Schnittstellen ICatund IDogund nicht verwandte Klassen Cat:ICatund Dog:IDog) wären die Vergleiche legal Whiskers==Fidound Whiskers==34legal (der erste könnte nur wahr sein, wenn Whiskers und Fido beide null wären, der zweite könnte niemals wahr sein ). Tatsächlich lehnt ein C # -Compiler beide ab. Whiskers==Fred;wird verboten, wenn Cates versiegelt ist, aber erlaubt, wenn es nicht ist.
Supercat
26

Wenn Sie den int-Wert 0(oder einen anderen Werttyp) in umwandelnobject , wird der Wert eingerahmt . Jede Umwandlung objecterzeugt eine andere Box (dh eine andere Objektinstanz). Der ==Operator für den objectTyp führt einen Referenzvergleich durch, gibt also false zurück, da die linke und die rechte Seite nicht dieselbe Instanz sind.

Wenn Sie dagegen Equalseine virtuelle Methode verwenden, wird die Implementierung des tatsächlichen Box-Typs verwendet, dh Int32.Equals, der true zurückgibt, da beide Objekte denselben Wert haben.

Thomas Levesque
quelle
18

Der ==Operator ist statisch und nicht virtuell. Es wird der genaue Code ausgeführt, den die objectKlasse definiert (`Objekt ist der Kompilierungszeittyp der Operanden), der unabhängig vom Laufzeittyp eines der beiden Objekte einen Referenzvergleich durchführt.

Die EqualsMethode ist eine virtuelle Instanzmethode. Es wird der Code ausgeführt, der im tatsächlichen Laufzeittyp des (ersten) Objekts definiert ist, nicht der Code in der objectKlasse. In diesem Fall ist das Objekt ein Objekt int, daher wird ein Wertevergleich durchgeführt, da dies vom intTyp für seine EqualsMethode definiert wird.

Servieren
quelle
Das ==Token repräsentiert tatsächlich zwei Operatoren, von denen einer überladbar ist und einer nicht. Das Verhalten des zweiten Operators unterscheidet sich stark von dem einer Überladung von (Objekt, Objekt).
Supercat
13

Die Equals()Methode ist virtuell.
Daher wird die konkrete Implementierung immer aufgerufen, auch wenn die Call-Site gegossen wird object. intÜberschreibt Equals()den Vergleich nach Wert, sodass Sie einen Wertvergleich erhalten.

SLaks
quelle
10

== Verwenden: Object.ReferenceEquals

Object.Equals vergleicht den Wert.

Die object.ReferenceEqualsMethode vergleicht Referenzen. Wenn Sie ein Objekt zuweisen, erhalten Sie zusätzlich zu den Daten des Objekts auf dem Speicherheap eine Referenz, die einen Wert enthält, der seinen Speicherort angibt.

Die object.EqualsMethode vergleicht den Inhalt von Objekten. Zunächst wird geprüft, ob die Referenzen gleich sind, ebenso wie object.ReferenceEquals. Dann werden jedoch abgeleitete Equals-Methoden aufgerufen, um die Gleichheit weiter zu testen. Sieh dir das an:

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

quelle
Obwohl es Object.ReferenceEqualssich wie eine Methode verhält, die den C # ==-Operator für ihre Operanden verwendet, verwendet der C # -Referenzgleichheitsoperatoroperator (der durch die Verwendung ==von Operandentypen dargestellt wird, für die keine Überladung definiert ist) eine spezielle Anweisung, anstatt aufzurufen ReferenceEquals. Des Weiteren , Object.ReferenceEqualswenn beide werden Operanden akzeptieren , die nur passieren passen könnte null sein und Operanden akzeptieren , die sein müssen typ gezwungen zu Objectund konnte somit nicht möglicherweise nichts gefunden, während die Referenzstellungs Version ==würde sich weigern , eine solche Nutzung zu kompilieren .
Supercat
9

Der C # -Operator verwendet das Token ==, um zwei verschiedene Operatoren darzustellen: einen statisch überladbaren Vergleichsoperator und einen nicht überladbaren Referenzvergleichsoperator. Wenn es auf das ==Token stößt , prüft es zunächst, ob eine Überlastung des Gleichheitstests vorliegt, die für die Operandentypen gilt. In diesem Fall wird diese Überlastung ausgelöst. Andernfalls wird geprüft, ob die Typen für den Referenzvergleichsoperator gelten. In diesem Fall wird dieser Operator verwendet. Wenn keiner der Operatoren auf die Operandentypen anwendbar ist, schlägt die Kompilierung fehl.

Der Code (Object)0sendet nicht nur ein Int32to Object: Int32wie alle Werttypen tatsächlich zwei Typen, von denen einer Werte und Speicherorte beschreibt (wie die wörtliche Null), sondern von nichts abgeleitet ist und einer beschreibt Haufen Objekte und stammt von Object; Da nur der letztere Typ gesendet werden kann, Objectmuss der Compiler ein neues Heap-Objekt dieses letzteren Typs erstellen. Jeder Aufruf von (Object)0erstellt ein neues Heap-Objekt, sodass die beiden Operanden ==unterschiedliche Objekte sind, von denen jedes unabhängig den Int32Wert 0 kapselt .

Für die Klasse Objectsind keine verwendbaren Überladungen für den Operator equals definiert. Folglich kann der Compiler den überladenen Gleichheitstestoperator nicht verwenden und greift auf die Verwendung des Referenzgleichheitstests zurück. Da sich die beiden Operanden ==auf unterschiedliche Objekte beziehen, wird ein Bericht erstellt false. Der zweite Vergleich ist erfolgreich, da eine Heap-Objekt-Instanz Int32gefragt wird, ob sie der anderen entspricht. Da diese Instanz weiß, was es bedeutet, einer anderen bestimmten Instanz gleich zu sein, kann sie antworten true.

Superkatze
quelle
Außerdem 0gehe ich davon aus , dass jedes Mal, wenn Sie ein Literal in Ihren Code schreiben, ein int-Objekt im Heap dafür erstellt wird. Es ist keine eindeutige Referenz auf einen globalen statischen Nullwert (wie sie String.Empty gemacht haben, um zu vermeiden, dass neue leere String-Objekte nur zum Initialisieren neuer Strings erstellt werden). Ich bin mir also ziemlich sicher, dass selbst das Ausführen von a 0.ReferenceEquals(0)false zurückgibt, da beide Nullen sind neu erstellte Int32Objekte.
Nyerguds
1
@Nyerguds, ich bin mir ziemlich sicher, dass alles, was Sie gesagt haben, in Bezug auf Ints, Heap, Verlauf, globale Statik usw. falsch ist, 0.ReferenceEquals(0)weil Sie versuchen, eine Methode für eine Kompilierungszeitkonstante aufzurufen. Es gibt kein Objekt zum Aufhängen. Ein int ohne Box ist eine Struktur, die auf dem Stapel gespeichert ist. Auch int i = 0; i.ReferenceEquals(...)wird nicht funktionieren. Weil System.Int32erbt NICHT von Object.
Andrew Backer
@ AndrewBacker, System.Int32ist ein struct, ein structist System.ValueType, das selbst erbt System.Object. Deshalb gibt es eine ToString()Methode und eine EqualsMethode fürSystem.Int32
Sebastian
1
Trotzdem hat Nyerguds zu Unrecht angegeben, dass auf dem Heap ein Int32 erstellt wird, was nicht der Fall ist.
Sebastian
@SebastianGodelet, ich ignoriere die Interna darin. System.Int32 selbst implementiert diese Methoden. GetType () ist extern Objectaktiviert, und hier habe ich vor langer Zeit aufgehört, mir darüber Sorgen zu machen. Es war nie notwendig, weiter zu gehen. AFAIK die CLR behandelt die beiden Typen unterschiedlich und speziell. Es ist nicht nur Vererbung. Es ist jedoch iseine von zwei Arten von Daten. Ich wollte nur nicht, dass jemand diesen Kommentar liest und so weit vom Kurs abweicht, einschließlich der Seltsamkeit gegenüber leeren Strings, die das Internieren von Strings ignoriert.
Andrew Backer
3

Beide Prüfungen sind unterschiedlich. Der erste prüft auf Identität , der zweite auf Gleichheit . Im Allgemeinen sind zwei Begriffe identisch, wenn sie sich auf dasselbe Objekt beziehen. Dies impliziert, dass sie gleich sind. Zwei Terme sind gleich, wenn ihre Werte gleich sind.

In Bezug auf die Programmierung wird Identität normalerweise durch Referenzgleichheit beeinträchtigt. Wenn der Zeiger auf beide Begriffe gleich (!) Ist, ist das Objekt, auf das sie zeigen, genau das gleiche. Wenn sich die Zeiger jedoch unterscheiden, kann der Wert der Objekte, auf die sie zeigen, immer noch gleich sein. In C # kann die Identität mit dem statischen Object.ReferenceEqualsElement überprüft werden, während die Gleichheit mit dem nicht statischen Element überprüft wird Object.Equals. Da Sie zwei Ganzzahlen in Objekte umwandeln (was übrigens als "Boxen" bezeichnet wird), führt der Operator ==von objectdie erste Prüfung durch, die standardmäßig zugeordnet ist, Object.ReferenceEqualsund prüft die Identität. Wenn Sie das nicht statische EqualsMitglied explizit aufrufen , führt der dynamische Versand zu einem Aufruf von Int32.Equals, der die Gleichheit überprüft.

Beide Konzepte sind ähnlich, aber nicht gleich. Sie mögen zunächst verwirrend erscheinen, aber der kleine Unterschied ist sehr wichtig! Stellen Sie sich zwei Personen vor, nämlich "Alice" und "Bob". Sie leben beide in einem gelben Haus. Unter der Annahme, dass Alice und Bob in einem Viertel leben, in dem Häuser nur in ihrer Farbe unterschiedlich sind, könnten sie beide in verschiedenen gelben Häusern leben. Wenn Sie beide Häuser vergleichen, werden Sie feststellen, dass sie absolut gleich sind, weil sie beide gelb sind! Sie teilen sich jedoch nicht dasselbe Haus und daher sind ihre Häuser gleich , aber nicht identisch . Identität würde bedeuten, dass sie im selben Haus leben.

Hinweis : Einige Sprachen definieren den ===Operator, der auf Identität überprüft werden soll.

Carsten
quelle