Was ist der Unterschied zwischen == und Equals () für Grundelemente in C #?


180

Betrachten Sie diesen Code:

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

Beide intund shortsind primitive Typen, aber ein Vergleich mit ==return true und ein Vergleich mit Equalsreturn false.

Warum?


9
@OrangeDog Bitte denken Sie über die Frage nach und stimmen Sie dann ab, um zu schließen

4
Dies fehlt der offensichtliche umgekehrte Versuch:Console.WriteLine(age.Equals(newAge));
ANeves

3
Das Duplikat erklärt dieses Verhalten nicht. Es geht nur darum, was Equals()im Allgemeinen ist.
SLaks

37
Genau diese Frage habe ich vor einigen Tagen im Coverity-Blog beantwortet. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert

5
@CodesInChaos: In der Spezifikation wird der Begriff "primitive Typen" tatsächlich zweimal verwendet, ohne ihn jemals zu definieren. Die Implikation ist, dass primitive Typen integrierte Werttypen sind, dies wird jedoch nie klargestellt. Ich habe Mads empfohlen, den Begriff einfach aus der Spezifikation zu streichen, da er mehr Verwirrung zu stiften scheint, als er beseitigt.
Eric Lippert

Antworten:


262

Kurze Antwort:

Gleichheit ist kompliziert.

Detaillierte Antwort:

Primitive Typen überschreiben die Basis object.Equals(object)und geben true zurück, wenn das Feld objectdenselben Typ und Wert hat. (Beachten Sie, dass dies auch für nullfähige Typen funktioniert. Nicht null-nullbare Typen können immer mit einer Instanz des zugrunde liegenden Typs verknüpft werden.)

Da newAgees sich um a handelt short, gibt die Equals(object)Methode nur dann true zurück, wenn Sie einen Boxed Short mit demselben Wert übergeben. Sie übergeben eine Box int, daher wird false zurückgegeben.

Im Gegensatz dazu wird der ==Operator so definiert, dass er zwei ints (oder shorts oder longs) nimmt.
Wenn Sie es mit einem intund einem aufrufen short, konvertiert der Compiler implizit das shortin intund vergleicht die resultierenden ints nach Wert.

Andere Möglichkeiten, damit es funktioniert

Primitive Typen haben auch eine eigene Equals()Methode, die denselben Typ akzeptiert.
Wenn Sie schreiben age.Equals(newAge), wählt der Compiler int.Equals(int)die beste Überladung aus und konvertiert implizit shortin int. Es wird dann zurückgegeben true, da diese Methode einfach das ints direkt vergleicht .

shorthat auch eine short.Equals(short)Methode, intkann aber nicht implizit konvertiert werden short, sodass Sie sie nicht aufrufen.

Sie könnten es zwingen, diese Methode mit einer Besetzung aufzurufen:

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

Dies wird short.Equals(short)direkt ohne Boxen aufgerufen . Wenn agegrößer als 32767 ist, wird eine Überlaufausnahme ausgelöst.

Sie können die short.Equals(object)Überladung auch aufrufen , aber ein Boxobjekt explizit übergeben, damit es denselben Typ erhält:

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

Wie bei der vorherigen Alternative wird dadurch ein Überlauf ausgelöst, wenn er nicht in eine passt short. Im Gegensatz zur vorherigen Lösung wird das shortObjekt in ein Objekt gepackt, wodurch Zeit und Speicher verschwendet werden.

Quellcode:

Hier sind beide Equals()Methoden aus dem eigentlichen Quellcode:

    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;
    }

Weiterführende Literatur:

Siehe Eric Lippert .


3
@SLaks, wenn wir anrufen long == int, intimplizit nach longrechts konvertiert ?
Selman Genç

1
Und ja, ich habe das alles aufgeschrieben, ohne es wirklich zu versuchen.
SLaks

1
Denken Sie daran , dass in dem Code der Frage, ob eine Änderung int age = 25;an const int age = 25;, dann ändert sich das Ergebnis. Dies liegt daran, dass in diesem Fall eine implizite Konvertierung von intnach shortvorhanden ist. Siehe Implizite Konvertierungen konstanter Ausdrücke .
Jeppe Stig Nielsen

2
@SLaks ja, aber der Wortlaut Ihrer Antwort "der übergebene Wert" kann in beide Richtungen interpretiert werden (als der vom Entwickler übergebene Wert oder der Wert, der nach dem Entpacken tatsächlich von der CLR übergeben wird). Ich vermute, der Gelegenheitsbenutzer, der die Antworten hier noch nicht kennt, wird sie als die ersteren lesen
JaredPar

2
@ Rachel: Nur dass das nicht stimmt; Der Standardoperator == vergleicht Referenztypen nach Referenz. Für Werttypen und für Typen, die überladen sind ==, ist dies nicht der Fall.
SLaks

55

Weil es keine Überlastung dafür gibt short.Equals, akzeptiert ein int. Daher heißt dies:

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

objist kein short.. daher ist es falsch.


12

Wenn Sie intan short's Equals übergeben, übergeben Sie object:

Geben Sie hier die Bildbeschreibung ein Dieser Pseudocode läuft also:

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


10

==wird zum Überprüfen einer Gleichheitsbedingung verwendet, kann als Operator (boolescher Operator) betrachtet werden, nur um zwei Dinge zu vergleichen, und hier spielt der Datentyp keine Rolle, da ein Typ-Casting durchgeführt würde, und Equalswird auch zum Überprüfen der Gleichheitsbedingung verwendet In diesem Fall sollten die Datentypen jedoch identisch sein. N Equals ist eine Methode, kein Operator.

Im Folgenden finden Sie ein kleines Beispiel aus dem von Ihnen bereitgestellten Beispiel, das den Unterschied in Kürze verdeutlicht.

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

Im obigen Beispiel haben X und Y die gleichen Werte, dh 1, und wenn wir verwenden ==, wird true zurückgegeben, da im Fall von ==der Short-Typ vom Compiler in int konvertiert wird und das Ergebnis angegeben wird.

und wenn wir verwenden Equals, wird der Vergleich durchgeführt, aber das Typ-Casting wird nicht vom Compiler durchgeführt, so dass false zurückgegeben wird.

Leute, bitte lasst es mich wissen, wenn ich falsch liege.


6

In vielen Kontexten, in denen eine Methode oder ein Operatorargument nicht vom erforderlichen Typ ist, versucht der C # -Compiler, eine implizite Typkonvertierung durchzuführen. Wenn der Compiler alle Argumente durch Hinzufügen impliziter Konvertierungen an seine Operatoren und Methoden anpassen kann, erfolgt dies ohne Beanstandung, auch wenn die Ergebnisse in einigen Fällen (insbesondere bei Gleichheitstests!) Überraschend sein können.

Ferner beschreibt jeder Werttyp wie intoder shorttatsächlich sowohl eine Art von Wert als auch eine Art von Objekt (*). Es gibt implizite Konvertierungen, um Werte in andere Arten von Werten zu konvertieren und um jede Art von Wert in die entsprechende Art von Objekt zu konvertieren, aber die verschiedenen Arten von Objekten sind nicht implizit ineinander konvertierbar.

Wenn man den ==Operator verwendet, um a shortund an zu vergleichen int, shortwird das implizit in a konvertiert int. Wenn sein numerischer Wert gleich dem von war int, entspricht der Wert , intmit dem er konvertiert wurde, dem, intmit dem er verglichen wird. Wenn man versucht, die EqualsMethode für den Kurzschluss zu verwenden, um sie mit einer zu vergleichen int, wäre die einzige implizite Konvertierung, die eine Überladung der EqualsMethode befriedigen würde, die Konvertierung in den entsprechenden Objekttyp int. Wenn das shortgefragt wird, ob es mit dem übergebenen Objekt übereinstimmt, wird es feststellen, dass das fragliche Objekt inteher ein als ein ist, shortund daraus schließen, dass es unmöglich gleich sein kann.

Obwohl sich der Compiler nicht darüber beschwert, sollte man im Allgemeinen vermeiden, Dinge zu vergleichen, die nicht vom gleichen Typ sind. Wenn man daran interessiert ist, ob die Konvertierung von Dingen in eine gemeinsame Form das gleiche Ergebnis liefern würde, sollte man eine solche Konvertierung explizit durchführen. Betrachten Sie zum Beispiel

int i = 16777217;
float f = 16777216.0f;

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

Es gibt drei Möglichkeiten, wie man ein intmit einem vergleichen möchte float. Man möchte vielleicht wissen:

  1. Entspricht der nächstmögliche floatWert intdem float?
  2. Stimmt die ganze Zahl mit dem floatüberein int?
  3. Machen Sie das intund floatrepräsentieren Sie den gleichen numerischen Wert.

Wenn man versucht, ein intund floatdirekt zu vergleichen , beantwortet der kompilierte Code die erste Frage; Ob der Programmierer dies beabsichtigt hat, ist jedoch alles andere als offensichtlich. Wenn Sie den Vergleich auf (float)i == fändern, wird klargestellt, dass die erste Bedeutung beabsichtigt war, oder (double)i == (double)fder Code beantwortet die dritte Frage (und es wird klargestellt, dass dies beabsichtigt ist).

(*) Selbst wenn die C # -Spezifikation einen Wert vom Typ betrachtet, z. System.Int32B. ein Objekt vom Typ System.Int32, wird einer solchen Ansicht die Anforderung widersprochen, dass ein Code auf einer Plattform ausgeführt wird, deren Spezifikation Werte und Objekte als Bewohner verschiedener Universen betrachtet. Wenn Tes sich um einen Referenztyp handelt und xes sich um einen handelt T, Tsollte sich eine Referenz vom Typ beziehen können x. Wenn also eine Variable vvom Typ Int32eine enthält Object, sollte eine Referenz vom Typ Objecteine Referenz voder deren Inhalt enthalten können. Tatsächlich könnte eine Referenz vom Typ Objectauf ein Objekt verweisen, das Daten enthält, die von kopiert wurden v, jedoch nicht auf sich vselbst oder dessen Inhalt. Das würde darauf hindeuten, dass wedervnoch sein Inhalt ist wirklich ein 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 intFalsch. Im Gegensatz zu Java verfügt C # nicht über separate primitive und Boxed-Typen. Es wird geboxt, objectweil das die einzige andere Überlastung von ist Equals().
SLaks

Die erste und dritte Frage sind identisch; Der genaue Wert ging bereits bei der Umstellung auf verloren float. Das Wirken von a floatzu a doublewird nicht auf magische Weise neue Präzision erzeugen.
SLaks

@SLaks: Gemäß der ECMA-Spezifikation, die die virtuelle Maschine beschreibt, auf der C # ausgeführt wird, erstellt jede Werttypdefinition zwei unterschiedliche Typen. Die C # -Spezifikation kann sagen, dass der Inhalt eines Speicherorts vom Typ List<String>.Enumeratorund eines Heap-Objekts vom Typ List<String>.Enumeratoridentisch ist, aber die ECMA / CLI-Spezifikation sagt, dass sie unterschiedlich sind, und selbst wenn sie in C # verwendet werden, verhalten sie sich unterschiedlich.
Supercat

@SLaks: Wenn iund vor dem Vergleich fjeweils konvertiert doublewürden, würden sie 16777217.0 und 16777216.0 ergeben, die als ungleich verglichen werden. Das Konvertieren i floatwürde 16777216.0f ergeben, verglichen mit f.
Supercat

@SLaks: Ein einfaches Beispiel für den Unterschied zwischen Speicherorttypen und Boxed-Object-Typen finden Sie in der Methode bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Der Boxed-Objekttyp, der einem Werttyp entspricht, kann den Parametertyp ReferenceEqualsüber einen identitätserhaltenden Upcast erfüllen. Der Speicherorttyp erfordert jedoch eine nicht identitätserhaltende Konvertierung. Wenn Gießen ein Tzu UAusbeuten einen Verweis auf etwas anderes als das Original Tzu mir, die nahe legen, dass ein Tnicht wirklich ein U.
Supercat

5

Equals () ist eine Methode der System.Object- Klassensyntax
: Public virtual bool Equals ()
Empfehlung Wenn wir den Status von zwei Objekten vergleichen möchten, sollten wir die Equals () -Methode verwenden

wie oben angegeben Antworten == Operatoren vergleichen die Werte sind gleich.

Bitte verwechseln Sie nicht ReferenceEqual

Reference Equals ()
Syntax: public static bool ReferenceEquals ()
Bestimmt, ob die angegebene Objektinstanz zur selben Instanz gehört


8
Dies beantwortet die Frage überhaupt nicht.
SLaks

SLaks, die ich nicht mit Beispielen erklärt habe, sind grundlegend für die obige Frage.
Sugat Mankar

4

Was Sie wissen müssen, ist, dass das Tun ==immer eine Methode aufruft. Die Frage ist, ob anrufen ==und Equalsam Ende die gleichen Dinge anrufen / tun.

Bei Referenztypen ==wird immer zuerst geprüft, ob die Referenzen identisch sind ( Object.ReferenceEquals). EqualsAuf der anderen Seite kann überschrieben werden und prüfen, ob einige Werte gleich sind.

BEARBEITEN: Um svick zu beantworten und den Kommentar von SLaks hinzuzufügen, hier ein IL-Code

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.

Also, welche Methode vergleicht man zwei ints mit ==? Hinweis: Es gibt keine operator ==Methode für Int32, aber es gibt eine fürString .
Svick

2
Dies beantwortet die Frage überhaupt nicht.
SLaks

@SLaks: Es beantwortet in der Tat nicht die spezifische Frage zu int und kurzen Vergleich, Sie haben es bereits beantwortet. Ich ==finde es immer noch interessant zu erklären, dass dies nicht nur Magie bewirkt, sondern schließlich einfach eine Methode aufruft (die meisten Programmierer haben wahrscheinlich nie einen Operator implementiert / überschrieben). Vielleicht hätte ich Ihrer Frage einen Kommentar hinzufügen können, anstatt meine eigene Antwort hinzuzufügen. Fühlen Sie sich frei, Ihre zu aktualisieren, wenn Sie der Meinung sind, dass das, was ich gesagt habe, relevant ist.
user276648

Beachten Sie, dass es sich ==bei primitiven Typen nicht um einen überladenen Operator handelt, sondern um eine intrinsische Sprachfunktion, die mit dem ceqIL-Befehl kompiliert wird .
SLaks

3

== Im Primitiven

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

Im primitiven Vergleich verhalten sich == Operatoren ziemlich offensichtlich. In C # sind viele == Operatorüberladungen verfügbar.

  • string == string
  • int == int
  • uint == uint
  • lang == lang
  • viel mehr

In diesem Fall erfolgt also keine implizite Konvertierung von int zu shortaber shortzu intist möglich. NewAge wird also in int konvertiert und es erfolgt ein Vergleich, der true zurückgibt, da beide den gleichen Wert haben. Es ist also gleichbedeutend mit:

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

.Equals () in Primitive

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

Hier müssen wir sehen, was die Equals () -Methode ist. Wir rufen Equals mit einer kurzen Typvariablen auf. Es gibt also drei Möglichkeiten:

  • Gleich (Objekt, Objekt) // statische Methode vom Objekt
  • Gleich (Objekt) // virtuelle Methode vom Objekt
  • Equals (short) // Implementiert IEquatable.Equals (short)

Der erste Typ ist hier nicht der Fall, da die Anzahl der Argumente unterschiedlich ist und wir nur ein Argument vom Typ int aufrufen. Drittens wird ebenfalls eliminiert, wie oben erwähnt. Eine implizite Umwandlung von int in short ist nicht möglich. Also hier wird der zweite Typ von Equals(object)genannt. Das short.Equals(object)ist:

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

Also hier wurde der Zustand getestet z is short die falsch ist, da z ein int ist und daher false zurückgibt.

Hier ist ein ausführlicher Artikel von Eric Lippert

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.