Egyenlőségi operátorok – annak vizsgálata, hogy két objektum egyenlő-e vagy sem

Az == (egyenlőség) és != az (egyenlőtlenségi) operátorok ellenőrzik, hogy operandusaik egyenlőek-e vagy sem. Az értéktípusok egyenlők, ha a tartalmaik egyenlőek. A referenciatípusok egyenlők, ha a két változó ugyanarra a tárolóra hivatkozik.

A C# nyelv referenciadokumentuma a C# nyelv legújabb kiadású verzióját ismerteti. Emellett a közelgő nyelvi kiadás nyilvános előzetes verziójú funkcióinak kezdeti dokumentációját is tartalmazza.

A dokumentáció azonosítja azokat a funkciókat, amelyeket először a nyelv utolsó három verziójában vagy az aktuális nyilvános előzetes verziókban vezetnek be.

Jótanács

Ha meg szeretné tudni, hogy mikor jelent meg először egy funkció a C#-ban, tekintse meg a C# nyelvi verzióelőzményeiről szóló cikket.

A mintamegfeleltetési operátort a teszt alternatívájaként is használhatja==, ha állandó értéken tesztel. Az is operátor az alapértelmezett egyenlőségi szemantikát használja az összes érték- és referenciatípushoz.

Egyenlőség operátor ==

Az egyenlőség operátor == akkor ad true vissza értéket, ha az operandusai egyenlőek, false ellenkező esetben.

Értéktípusok egyenlősége

A beépített értéktípusok operandusai egyenlők, ha az értékük egyenlő:

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

Feljegyzés

Ha az ==<><=>= operandusok egyike nem szám (Double.NaN vagy Single.NaN), akkor a művelet falseeredménye . Ez a feltétel azt jelenti, hogy az NaN érték nem nagyobb, mint bármely más double (vagy float) érték, beleértve a NaN. További információkért és példákért tekintse meg vagy tekintse meg a Double.NaNSingle.NaN referenciacikket.

Az azonos számtípusú két operandus egyenlő, ha az alapul szolgáló integráltípus megfelelő értékei egyenlők.

A felhasználó által definiált szerkezettípusok alapértelmezés szerint nem támogatják az == operátort. Az operátor támogatásához a == felhasználó által definiált szerkezetnek túl kell terhelnie azt.

A C#-tuples beépített támogatást nyújt az és == az != operátorok számára. További információkért tekintse meg a Tuple-típusok cikkÉnek Tuple egyenlőségi szakaszát.

Hivatkozástípusok egyenlősége

Alapértelmezés szerint a hivatkozás típusú operandusok, a rekordok kivételével, egyenlőek, ha ugyanarra az objektumra hivatkoznak:

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

Ahogy az előző példa is mutatja, a felhasználó által definiált referenciatípusok alapértelmezés szerint támogatják az == operátort. A referenciatípus azonban túlterhelheti az operátort == . Ha egy hivatkozástípus túlterheli az == operátort, a Object.ReferenceEquals módszerrel ellenőrizheti, hogy az adott típus két hivatkozása ugyanarra az objektumra hivatkozik-e.

Rekordtípusok egyenlősége

A rekordtípusok támogatják azokat az == operátorokat, != amelyek alapértelmezés szerint értékegyenlőségi szemantikát biztosítanak. Ez azt jelzi, hogy két rekord operandusa egyenlő, ha mindkettő null vagy az összes mező megfelelő értéke, és az automatikusan implementált tulajdonságok egyenlők.

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

Ahogy az előző példa is mutatja, a referencia típusú tagok egyenlőségét összehasonlítjuk a saját egyenlőségi megvalósításukkal.

Sztring egyenlősége

Két sztringoperndus akkor egyenlő, ha mindkettő null azonos hosszúságú, vagy mindkét sztringpéldány azonos hosszúságú, és mindegyik karakterpozícióban azonos karakterekkel rendelkezik:

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

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

A sztringek egyenlőségének összehasonlítása kis- és nagybetűk közötti összehasonlítás. A sztringek összehasonlításáról további információt a Sztringek összehasonlítása C#-ban című témakörben talál.

Egyenlőség delegálása

Két azonos futásidejű delegált operandus egyenlő, ha mindkettő azonos null hosszúságú, vagy a meghívási listák azonos hosszúságúak, és mindegyik pozícióban azonos bejegyzésekkel rendelkeznek:

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

Fontos

A meghívási lista egyenlő bejegyzései tartalmazzák a hívásban szereplő összes rögzített paramétert, beleértve a fogadót is. A fogadó egy objektum példánya, amelyet this a bejegyzés meghívása jelöl.

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)

További információ: A C# nyelvi specifikációjának Egyenlőségi operátorok delegálása szakasza.

A szemantikailag azonos lambdakifejezések kiértékeléséből származó meghatalmazottak nem egyenlők, ahogyan az alábbi példa is mutatja:

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

Egyenlőtlenség operátor !=

Az egyenlőtlenségi operátor != akkor ad true vissza értéket, ha az operandusai nem egyenlők, és false ellenkező esetben. A beépített típusok operandusai A típusegyenlőségről további információt az Egyenlőség operátor szakaszban talál.

Az alábbi példa bemutatja az operátor használatát != :

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

Egyenlőség az osztályhierarchiákban

A rekordok manuális munka nélkül helyesen kezelik az öröklést. A fordító által létrehozott egyenlőség a futtatókörnyezet típusát és az összes deklarált tulajdonságot is ellenőrzi, így automatikusan megfelel a szimmetrikus és tranzitivitási követelményeknek. Előnyben részesítse record a manuális rendezetlen hierarchiát, ha az értékegyenlőség a cél.

Fontos

Használja record , amikor csak lehetséges – a fordító létrehozza az összes szükséges egyenlőségi tagot. Manuális implementálásra csak akkor van szükség, ha a típusnak nem rekordosztályból kell származnia, vagy más korlátozásokkal kell rendelkeznie, amelyek megakadályozzák record.

Az egyenlőség saját kezű megvalósítása, ha egy típus nem lehet rekord

Íme egy minimális manuális implementáció egy olyan értéktípushoz, amely nem lehet rekord:

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

Az implementáció három szükséges tagot biztosít: Equals(T?) az alapvető összehasonlítást override Equals(object?) az objektumszintű hívásokhoz, és override GetHashCode() így a kivonatalapú gyűjtemények megfelelően működnek. HashCode.Combine egy olyan kódtár-segéd, amely a függvény által Equalshasznált értékekből készít kivonatot. A megvalósítás IEquatable<T> (a Equals(T?) túlterhelés) nem kötelező, de elkerüli a boxolást, ha a hívók már rendelkeznek a konkrét típussal.

A definiáláskor == és !=a nyelvben párként is szükség van rájuk; a CS0660 és a CS0661 figyelmeztetések figyelmeztetik, hogy mind a négy tag konzisztens maradjon.

A fenti három tag az értékegyenlőséget tükrözi, Equals de == továbbra is teszteli az identitást, mert még nem == deklarált operátort:

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)

A megfelelő végrehajtásnak meg kell felelnie az egyenértékűségi szerződésnek is (feltételezzük x, yés z nem null értékűek):

  1. Reflexív: x.Equals(x) visszaadja true.
  2. Szimmetrikus: x.Equals(y) ugyanazt az értéket adja vissza, mint a y.Equals(x).
  3. Tranzitív: ha x.Equals(y) és y.Equals(z) mindkettő true, akkor x.Equals(z) kell lennie true.
  4. Konzisztens: egymást követő hívások, hogy x.Equals(y) ugyanazt az értéket adja vissza, amíg egyik objektum sem változik.
  5. Null viselkedés: x.Equals(null) visszaadja false; x.Equals(y) nem szabad dobni, ha nem null xértékűre van meghívva.

A rendezetlen osztályhierarchiák értékegyenlősége nagyobb körültekintést igényel, mint egy lezárt osztályban a szimmetrikus és tranzitív szabályok kielégítése érdekében. A veszély az, hogy IEquatable<T>.Equals(T? other) a küldés a változó deklarált típusát (a változó deklarációjában írt típust) követi, nem pedig a futtatókörnyezet típusát. Ha Shape nemetEquals(Shape? other)virtual deklarál, a futtatókörnyezetben lévőként ShapeCircle beírt változó meghívja Shape.Equals– csendesen figyelmen kívül hagyva az Circleadott mezőket. Két Circle különböző radiival rendelkező objektum egyenlőként hasonlítható össze, ha egy Shape változón keresztül fér hozzá.

A helyes minta két együttműködési követelményt igényel: a gépelt Equals metódust virtual úgy kell létrehozni, hogy minden származtatott osztály ki tudja terjeszteni az összehasonlítást, és adjon hozzá egy GetType() == other.GetType() védőt az alaposztály implementációjához, így a különböző futtatókörnyezet-típusok objektumai soha nem tekinthetők egyenlőnek.

Alaposztály implementálása

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

Főbb pontok:

  • virtual gépelt Equals: minden származtatott osztály felülbírálja ezt a módszert, hogy az összehasonlítást a saját mezőivel egészíthesse ki.
  • GetType() őr: GetType() == other.GetType() megakadályozza, hogy egy CircleShape azonos színű objektum egyenlő legyen, és megakadályozza, hogy a különböző származtatott típusú objektumok egyenlők egymással.
  • GetHashCode tartalmazza: GetType()mivel két objektum csak akkor egyenlő, ha a futtatókörnyezet-típusok megegyeznek, GetHashCode a futtatókörnyezet típusát és az adatmezőket is kivonatelni kell. Az itt való kihagyás GetType() helytelen viselkedést okoz az és HashSet<T>a Dictionary<TKey,TValue> .
  • == a következőre Equalsdelegáltak: konzisztens marad az operátorok és a metódusok egyenlősége.

Származtatott osztály implementálása

Egy származtatott osztály, amely mezőket ad hozzá, felülbírálja a gépeltet Equals, saját típusra vetíti, meghívja base.Equals, majd összehasonlítja a saját mezőit:

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) kényszeríti az őrt GetType() , és ellenőrzi a megosztott mezőket. A leadás other is Circle c gyorsan meghiúsul, ha az argumentum Shape bármilyen más származtatott típusból áll.

Használat alaptípusú változón keresztül

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)

A lezárt osztályok egyszerűbbek

Az osztályokat nem lehet alosztályba sorolni sealed , ezért a fordítási idő és a futtatókörnyezet típusok mindig megegyeznek. Nincs szükség az őrre vagy virtual a GetType() diszpécserre. Ha IEquatable<T>egy típus nem lehet rekord , akkor a minta akkor jelenik meg, ha egy lezárt osztály esetében egy típus nem lehet helyes és teljes.

Operátorok túlterhelése

A felhasználó által definiált típusban túlterhelheti az operátorokat és == az != operátorokat. Ha a két operátor egyikét túlterheli, akkor a másik operátort is túl kell terhelnie.

A rekordtípusú operátorokat és == operátorokat != nem lehet explicit módon túlterhelni. A rekordtípus ==és != az T operátorok viselkedésének módosításához implementálja a metódust IEquatable<T>.Equals a következő aláírással:

public virtual bool Equals(T? other);

C# nyelvspecifikáció

További információ: A C# nyelvi specifikációjának relációs és típustesztelő operátorok szakasza.

A rekordtípusok egyenlőségével kapcsolatos további információkért tekintse meg a C#-nyelv specifikációjánakEgyenlőség tagjai szakaszát.

Lásd még