Gleichheitsoperatoren: Testen, ob zwei Objekte gleich oder ungleich sind

Die Operatoren == (Gleichheit) und != (Ungleichheit) überprüfen, ob die Operanden gleich sind oder nicht. Werttypen sind gleich, wenn ihr Inhalt gleich ist. Verweistypen sind gleich, wenn die beiden Variablen auf denselben Speicher verweisen.

Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie eine erste Dokumentation zu Funktionen in der öffentlichen Vorschau für die kommende Sprachversion.

In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.

Tipp

Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.

Sie können den is Musterabgleichsoperator als Alternative zu einem == Test verwenden, wenn Sie einen Konstantenwert testen. Der is Operator verwendet die Standardgleichheitssemantik für alle Wert- und Referenztypen.

Gleichheitsoperator ==

Der Gleichheitsoperator == gibt true zurück, wenn die Operanden gleich sind; andernfalls false.

Gleichheit von Werttypen

Operanden der integrierten Werttypen sind gleich, wenn ihre Werte gleich sind:

int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b);  // output: True

char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2);  // output: False
Console.WriteLine(c1 == char.ToLower(c2));  // output: True

Hinweis

Für die ==Operatoren ,< , >und , <=>= wenn einer der Operanden keine Zahl (Double.NaN oder Single.NaN) ist, ist falsedas Ergebnis des Vorgangs . Diese Bedingung bedeutet, dass der NaN Wert nicht größer als, kleiner oder gleich einem anderen double (oder float) Wert ist, einschließlich NaN. Weitere Informationen und Beispiele finden Sie im Double.NaN- oder Single.NaN-Referenzartikel.

Zwei Operanden desselben enum-Typs sind gleich, wenn die entsprechenden Werte des zugrunde liegenden integralen Typs gleich sind.

Benutzerdefinierte Strukturtypen unterstützen den ==-Operator nicht standardmäßig. Eine benutzerdefinierte Struktur muss den ==-Operator überladen, damit er unterstützt wird.

C#- Tupel verfügen über integrierte Unterstützung für die == Und-Operatoren != . Weitere Informationen finden Sie im Abschnitt Tupelgleichheit im Artikel Tupeltypen.

Gleichheit von Verweistypen

Standardmäßig sind Verweistypopernden, mit Ausnahme von Datensätzen, gleich, wenn sie auf dasselbe Objekt verweisen:

public class ReferenceTypesEquality
{
    public class MyClass
    {
        private int id;

        public MyClass(int id) => this.id = id;
    }

    public static void Main()
    {
        var a = new MyClass(1);
        var b = new MyClass(1);
        var c = a;
        Console.WriteLine(a == b);  // output: False
        Console.WriteLine(a == c);  // output: True
    }
}

Wie im vorherigen Beispiel gezeigt, unterstützen benutzerdefinierte Verweistypen standardmäßig den == Operator. Ein Verweistyp kann den Operator == aber überladen. Wenn ein Verweistyp den ==-Operator überlädt, verwenden Sie die Object.ReferenceEquals-Methode, um zu überprüfen, ob zwei Verweise dieses Typs auf dasselbe Objekt verweisen.

Gleichheit von Datensatztypen

Datensatztypen unterstützen die Operatoren == und !=, die standardmäßig Semantik für Wertgleichheit ausdrücken. Das heißt, zwei Datensatzopernden sind gleich, wenn beide werte oder entsprechende Werte aller Felder sind null und automatisch implementierte Eigenschaften gleich sind.

public class RecordTypesEquality
{
    public record Point(int X, int Y, string Name);
    public record TaggedNumber(int Number, List<string> Tags);

    public static void Main()
    {
        var p1 = new Point(2, 3, "A");
        var p2 = new Point(1, 3, "B");
        var p3 = new Point(2, 3, "A");

        Console.WriteLine(p1 == p2);  // output: False
        Console.WriteLine(p1 == p3);  // output: True

        var n1 = new TaggedNumber(2, new List<string>() { "A" });
        var n2 = new TaggedNumber(2, new List<string>() { "A" });
        Console.WriteLine(n1 == n2);  // output: False
    }
}

Wie im vorstehenden Beispiel gezeigt, wird die Gleichheit von Referenztypmitgliedern mit ihren spezifischen Gleichheitsimplementierungen verglichen.

Zeichenfolgengleichheit

Zwei Zeichenfolge-Operanden gleich sind, wenn beide gleich null sind oder wenn beide Zeichenfolgeninstanzen dieselbe Länge und identische Zeichen in jeder Zeichenposition haben:

string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower());  // output: True

string s3 = "Hello!";
Console.WriteLine(s1 == s3);  // output: False

Bei Vergleichen der Gleichheit von Zeichenfolgen wird Groß-/Kleinschreibung beachtet. Weitere Informationen zum Zeichenfolgenvergleich finden Sie unter Vergleichen von Zeichenfolgen in C#.

Delegatengleichheit

Zwei Stellvertretungsopernden des gleichen Laufzeittyps sind gleich, wenn beide oder deren Aufruflisten dieselbe Länge haben null und in jeder Position gleiche Einträge haben:

Action a = () => Console.WriteLine("a");

Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c));  // output: False
Console.WriteLine(b == c);  // output: True

Von Bedeutung

Gleiche Einträge in einer Aufrufliste enthalten alle festen Parameter im Aufruf, einschließlich des Empfängers. Der Empfänger ist die Instanz eines Objekts, das dargestellt this wird, wenn der Eintrag aufgerufen wird.

var o1 = new object();
var o2 = new object();
var d1 = o1.ToString;
var d2 = o2.ToString;
Console.WriteLine(object.ReferenceEquals(d1, d2));  // output: False
Console.WriteLine(d1 == d2);  // output: False (different receivers)

Weitere Informationen finden Sie im Abschnitt Operatoren für Delegatengleichheit der C#-Sprachspezifikation.

Stellvertretungen, die aus der Auswertung semantisch identischer Lambda-Ausdrücke stammen, sind nicht gleich, wie das folgende Beispiel zeigt:

Action a = () => Console.WriteLine("a");
Action b = () => Console.WriteLine("a");

Console.WriteLine(a == b);  // output: False
Console.WriteLine(a + b == a + b);  // output: True
Console.WriteLine(b + a == a + b);  // output: False

Ungleichheitsoperator !=

Der Ungleichheitsoperator != gibt zurück true , wenn die Operanden nicht gleich sind und false andernfalls. Für die Operanden der integrierten Typen führt der Ausdruck x != y zum selben Ergebnis wie der Ausdruck !(x == y). Weitere Informationen zur Typengleichheit finden Sie im Abschnitt Gleichheitsoperator.

Im folgenden Beispiel wird die Verwendung des != Operators veranschaulicht:

int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b);  // output: True

string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2);  // output: False

object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2);  // output: True

Gleichheit in Klassenhierarchien

Datensätze behandeln die Vererbung ordnungsgemäß ohne manuelle Arbeit. Die vom Compiler generierte Gleichheit überprüft sowohl den Laufzeittyp als auch alle deklarierten Eigenschaften, sodass sie automatisch die Symmetrie- und Transitivitätsanforderungen erfüllt. Bevorzugen Sie record es vor einer manuellen unsealierten Hierarchie, wenn die Wertgleichstellung das Ziel ist.

Von Bedeutung

Verwenden Sie record nach Möglichkeit – der Compiler generiert alle erforderlichen Gleichheitselemente für Sie. Manuelle Implementierung ist nur erforderlich, wenn Ihr Typ von einer Nicht-Datensatzklasse abgeleitet werden muss oder andere Einschränkungen aufweist, die verhindert recordwerden.

Gleichheit selbst implementieren, wenn ein Typ kein Record sein kann

Hier ist eine minimale manuelle Implementierung für einen Werttyp, der kein Datensatz sein kann:

class Color : IEquatable<Color>
{
    public Color(int r, int g, int b)
    {
        R = r;
        G = g;
        B = b;
    }

    public int R { get; }
    public int G { get; }
    public int B { get; }

    public bool Equals(Color? other) =>
        other is not null && R == other.R && G == other.G && B == other.B;

    public override bool Equals(object? obj) => obj is Color other && Equals(other);
    public override int GetHashCode() => HashCode.Combine(R, G, B);
}

Die Implementierung stellt drei erforderliche Elemente bereit: Equals(T?) als Kernvergleich für override Equals(object?) Aufrufe auf Objektebene und override GetHashCode() damit hashbasierte Auflistungen ordnungsgemäß funktionieren. HashCode.Combine ist ein Bibliothekshilfsprogramm, das einen Hash aus denselben Werten erstellt, die von Equals. Die Implementierung IEquatable<T> (die Equals(T?) Überladung) ist optional, verhindert jedoch das Boxen, wenn Anrufer bereits über den konkreten Typ verfügen.

Wenn Sie diese auch als Paar definieren und !=festlegen==, erfordert die Sprache diese. Warnungen CS0660 und CS0661 erinnern Sie daran, alle vier Member konsistent zu halten.

Wenn die oben genannten Equals drei Mitglieder die Wertgleichheit widerspiegeln, die Identität aber == dennoch überprüft werden, weil noch kein == Operator deklariert wurde:

var red1 = new Color(255, 0, 0);
var red2 = new Color(255, 0, 0);

Console.WriteLine(red1.Equals(red2)); // => True
Console.WriteLine(red1 == red2);      // => False  (no == overload; identity check)

Eine korrekte Implementierung muss auch den Äquivalenzvertrag erfüllen (vorausgesetzt x, yund z sind ungleich NULL):

  1. Reflexiv: x.Equals(x) gibt zurück true.
  2. Symmetrisch: x.Equals(y) gibt denselben Wert wie y.Equals(x).
  3. Transitiv: wenn x.Equals(y) und y.Equals(z) beides sind true, muss es x.Equals(z) sich um true.
  4. Konsistent: Aufeinander folgende Aufrufe, um x.Equals(y) denselben Wert zurückzugeben, solange keines der Objekte geändert wird.
  5. Null-Verhalten: x.Equals(null) Gibt zurück false; x.Equals(y) darf nicht ausgelöst werden, wenn für einen Nicht-Null-Wert xaufgerufen wird.

Die Wertgleichheit in einer unsealierten Klassenhierarchie erfordert mehr Sorgfalt als in einer versiegelten Klasse, um die symmetrischen und transitiven Regeln zu erfüllen. Die Gefahr besteht darin, dass IEquatable<T>.Equals(T? other) die Versendung den deklarierten Typ (der in der Variablendeklaration geschriebene Typ) der Variablen und nicht dem Laufzeittyp folgt. Wenn Shape eine Nicht-DeklarierungvirtualEquals(Shape? other) erfolgt, wird eine Variable, die so eingegeben wird, wie Shape sie zur Circle Laufzeit aufgerufen wird Shape.Equals, im Hintergrund Circle-spezifische Felder ignoriert. Zwei Circle Objekte mit unterschiedlichen Bogenmaßen können beim Zugriff über eine Shape Variable gleich verglichen werden.

Das richtige Muster erfordert zwei kooperierende Anforderungen: Stellen Sie die typierte Methode virtual so, dass jede abgeleitete Equals Klasse den Vergleich erweitern kann, und fügen Sie in der Basisklassenimplementierung einen GetType() == other.GetType() Guard hinzu, sodass Objekte unterschiedlicher Laufzeittypen niemals gleich angesehen werden.

Basisklassenimplementierung

// Shape is an unsealed base class. Making Equals virtual and guarding with GetType()
// ensures a derived instance is never equal to an instance of a different runtime type.
class Shape : IEquatable<Shape>
{
    public string Color { get; }
    public Shape(string color) => Color = color;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // virtual so derived classes can override and augment the comparison
    public virtual bool Equals(Shape? other) =>
        other is not null &&
        GetType() == other.GetType() &&   // reject different runtime types
        Color == other.Color;

    // GetType() is included because equality requires matching runtime types
    public override int GetHashCode() => HashCode.Combine(GetType(), Color);

    public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null;
    public static bool operator !=(Shape? l, Shape? r) => !(l == r);
}

Wichtige Punkte:

  • virtual typed Equals: Jede abgeleitete Klasse setzt diese Methode außer Kraft, um den Vergleich mit eigenen Feldern zu erweitern.
  • GetType() guard: GetType() == other.GetType() verhindert, dass ein CircleShape Gleichheitszeichen mit derselben Farbe besteht, und verhindert, dass Objekte unterschiedlicher abgeleiteter Typen einander entsprechen.
  • GetHashCode includes GetType(): Because two objects are equal only when their runtime types match, GetHashCode must hash the runtime type as well the data fields. Das Auslassen GetType() hier führt zu falschem Verhalten in Dictionary<TKey,TValue> und HashSet<T>.
  • == Stellvertretungen an Equals: hält die Gleichheit von Operatoren und Methoden konsistent.

Abgeleitete Klassenimplementierung

Eine abgeleitete Klasse, die Felder hinzufügt, überschreibt den typierten EqualsTyp, wandelt in einen eigenen Typ um, ruft auf base.Equalsund vergleicht dann seine eigenen Felder:

class Circle : Shape
{
    public double Radius { get; }
    public Circle(string color, double radius) : base(color) => Radius = radius;

    public override bool Equals(object? obj) => Equals(obj as Shape);

    // Calls base.Equals to verify Color and runtime type, then adds Radius
    public override bool Equals(Shape? other) =>
        other is Circle c && base.Equals(c) && Radius == c.Radius;

    public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius);
}

base.Equals(c) erzwingt den GetType() Schutz und überprüft die freigegebenen Felder. Der Umwandlungsweg other is Circle c schlägt schnell fehl, wenn das Argument ein Shape anderer abgeleiteter Typ ist.

Verwendung über eine Basistypvariable

Shape circle1 = new Circle("red", 5.0);
Shape circle2 = new Circle("red", 7.0);
Shape circle3 = new Circle("red", 5.0);
Shape shape1  = new Shape("red");

Console.WriteLine(circle1.Equals(circle2)); // => False  (Radius differs)
Console.WriteLine(circle1.Equals(circle3)); // => True
Console.WriteLine(circle1.Equals(shape1));  // => False  (different runtime types)

Versiegelte Klassen sind einfacher

Sie können eine sealed Klasse nicht unterklassen, sodass kompilierte Zeit- und Laufzeittypen immer zustimmen. Sie benötigen nicht die Wache oder virtual den GetType() Versand. Das IEquatable<T> Muster in " Gleichheit selbst implementieren", wenn ein Typ kein Datensatz sein kann , ist korrekt und vollständig für eine versiegelte Klasse.

Operatorüberladbarkeit

Sie können die Operatoren und == Operatoren in einem benutzerdefinierten Typ !=. Wenn Sie einen dieser beiden Operatoren überladen, müssen Sie auch den anderen Operator überladen.

Sie können die == Operatoren und != Operatoren in einem Datensatztyp nicht explizit überladen. Um das Verhalten der == Datensätze != und Operatoren für den Datensatztyp Tzu ändern, implementieren Sie die IEquatable<T>.Equals Methode mit der folgenden Signatur:

public virtual bool Equals(T? other);

C#-Sprachspezifikation

Weitere Informationen finden Sie im Abschnitt Relationale und Typtestoperatoren in der C#-Sprachspezifikation.

Weitere Informationen zur Gleichheit von Datensatztypen finden Sie im Abschnitt " Gleichheitsmitglieder " der C#-Sprachspezifikation.

Weitere Informationen