Operátory rovnosti – test, jestli jsou dva objekty stejné nebo ne

Operátory == (rovnost) a!= kontrolují, jestli jsou jejich operandy stejné nebo ne. Typy hodnot jsou stejné, pokud se jejich obsah rovná. Odkazové typy jsou stejné, když obě proměnné odkazují na stejné úložiště.

Referenční dokumentace jazyka C# dokumentuje naposledy vydané verze jazyka C#. Obsahuje také počáteční dokumentaci k funkcím ve verzi Public Preview pro nadcházející jazykovou verzi.

Dokumentace identifikuje všechny funkce, které byly poprvé představeny v posledních třech verzích jazyka nebo v aktuálních verzích Public Preview.

Návod

Informace o tom, kdy byla funkce poprvé představena v jazyce C#, najdete v článku o historii verzí jazyka C#.

Operátor porovnávání vzorů is můžete použít jako alternativu == k testu, když testujete proti konstantní hodnotě. Operátor is používá výchozí sémantiku rovnosti pro všechny typy hodnot a odkazů.

Operátor rovnosti ==

Operátor == rovnosti vrátí true , pokud jsou jeho operandy stejné, false jinak.

Rovnost typů hodnot

Operandy předdefinovaných typů hodnot jsou stejné, pokud jsou jejich hodnoty stejné:

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

Poznámka:

==Pro operátory , ,<> , <=a >= operátory, pokud některý z operandů není číslo (Double.NaN nebo Single.NaN), výsledek operace je false. Tato podmínka znamená, že NaN hodnota není větší než, menší než nebo rovna žádné jiné double (nebo float) hodnotě, včetně NaN. Další informace a příklady najdete v článku nebo Double.NaN v referenčním Single.NaN článku.

Dva operandy stejného typu výčtu jsou stejné, pokud jsou odpovídající hodnoty základního celočíselného typu stejné.

Uživatelem definované typy struktur ve výchozím nastavení operátor nepodporují == . Pro podporu operátoru == musí uživatelem definovaná struktura přetížit .

Řazené kolekce členů jazyka C# mají integrovanou podporu pro operátory == a != operátory. Další informace najdete v oddílu rovnosti řazené kolekce členů v článku o typech řazených kolekcí členů.

Rovnost typů odkazů

Ve výchozím nastavení jsou operandy typu odkaz, s výjimkou záznamů, stejné, pokud odkazují na stejný objekt:

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

Jak ukazuje předchozí příklad, uživatelem definované odkazové typy podporují == operátor ve výchozím nastavení. Typ odkazu však může přetížit == operátor. Pokud typ odkazu přetíží == operátor, pomocí Object.ReferenceEquals metody zkontrolujte, zda dva odkazy tohoto typu odkazují na stejný objekt.

Rovnost typů záznamů

Typy záznamů podporují operátory == , != které ve výchozím nastavení poskytují sémantiku rovnosti hodnot. To znamená, že dva operandy záznamů jsou stejné, pokud jsou null oba nebo odpovídající hodnoty všech polí a automaticky implementované vlastnosti jsou stejné.

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

Jak ukazuje předchozí příklad, porovnává se rovnost členů typu odkazu pomocí jejich konkrétních implementací rovnosti.

Rovnost řetězců

Dva řetězcové operandy jsou stejné, pokud jsou null obě nebo obě instance řetězců mají stejnou délku a mají stejné znaky v každé pozici znaku:

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

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

Porovnání rovnosti řetězců jsou porovnání s rozlišováním malých a velkých písmen. Další informace o porovnání řetězců naleznete v tématu Porovnání řetězců v jazyce C#.

Delegování rovnosti

Dva operandy delegáta stejného typu běhu jsou stejné, pokud jsou null oba nebo jejich seznamy vyvolání mají stejnou délku a mají stejné položky v každé pozici:

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

Důležité

Stejné položky v seznamu volání zahrnují všechny pevné parametry v vyvolání, včetně příjemce. Příjemce je instance objektu reprezentovaného this při vyvolání položky.

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)

Další informace naleznete v části Delegovat operátory rovnosti specifikace jazyka C#.

Delegáti, kteří pocházejí z vyhodnocení sémanticky identických výrazů lambda , nejsou rovni, jak ukazuje následující příklad:

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

Operátor nerovnosti !=

Operátor != nerovnosti vrátí true , pokud jeho operandy nejsou rovny, a false jinak. Pro operandy předdefinovaných typů výraz x != y vytvoří stejný výsledek jako výraz !(x == y). Další informace o rovnosti typů naleznete v části Operátor rovnosti.

Následující příklad ukazuje použití operátoru != :

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

Rovnost v hierarchiích tříd

Záznamy zpracovávají dědičnost správně bez ruční práce. Vygenerovaná kompilátorem kontroluje rovnost jak typ modulu runtime, tak všechny deklarované vlastnosti, takže automaticky splňuje požadavky na symetrii a tranzitivitu. Preferujte record před ruční nezapečetěnou hierarchií, pokud je cílem rovnost hodnot.

Důležité

Kdykoli record je to možné , kompilátor za vás vygeneruje všechny požadované členy rovnosti. Ruční implementace je nutná pouze v případě, že typ musí být odvozen z třídy bez záznamu nebo má jiná omezení, která brání record.

Implementujte porovnávání rovnosti ručně, když typ nemůže být záznam

Tady je minimální ruční implementace pro typ hodnoty, který nemůže být záznam:

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

Implementace poskytuje tři požadované členy: Equals(T?) jako porovnání jádra, override Equals(object?) pro volání na úrovni objektu a override GetHashCode() proto kolekce založené na hodnotě hash fungují správně. HashCode.Combine je pomocník knihovny, který vytváří jednu hodnotu hash ze stejných hodnot, které Equalspoužívá . Implementace IEquatable<T> (přetížení) je volitelná, Equals(T?) ale zabraňuje boxování, pokud volající již mají konkrétní typ.

Když také definujete == a !=jazyk je vyžaduje jako pár. Upozornění CS0660 a CS0661 vám připomenou, abyste měli všechny čtyři členy konzistentní.

S výše uvedenými třemi členy odráží rovnost hodnot, ale == stále testuje identitu, Equals protože ještě nebyl deklarován žádný == operátor:

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)

Správná implementace musí také splňovat smlouvu o ekvivalenci (předpokládat x, ya z jsou nenulové):

  1. Reflexivní: x.Equals(x) vrátí true.
  2. Symetrické: x.Equals(y) vrátí stejnou hodnotu jako y.Equals(x).
  3. Tranzitivní: pokud x.Equals(y) a jsou oba truey.Equals(z) , pak x.Equals(z) musí být true.
  4. Konzistentní: následná volání, která x.Equals(y) vrátí stejnou hodnotu, pokud se žádný objekt nezmění.
  5. Chování null: x.Equals(null) vrátí false; x.Equals(y) nesmí vyvolat při zavolání na non-null x.

Rovnost hodnot v nezapečetěné hierarchii tříd vyžaduje větší péči než v zapečetěné třídě, aby vyhovovala symetrickým a tranzitivním pravidlům. Nebezpečí je, že IEquatable<T>.Equals(T? other) odeslání následuje deklarovaný typ (typ napsaný v deklaraci proměnné) proměnné, nikoli jeho typ modulu runtime. Pokud Shape deklaruje nevirtualEquals(Shape? other), proměnná typu, která Shape obsahuje Circle vyvolání Shape.Equalsza běhu – bezobslužné ignorování Circlepolí specifických pro konkrétní pole. Dva Circle objekty s různými paprsky se můžou při přístupu prostřednictvím Shape proměnné porovnávat jako stejné.

Správný vzor vyžaduje dva spolupracující požadavky: nastavte typovou metoduvirtual, aby každá odvozená Equals třída může rozšířit porovnání, a přidat GetType() == other.GetType() ochranu v implementaci základní třídy, takže objekty různých typů modulu runtime se nikdy nepovažují za stejné.

Implementace základní třídy

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

Klíčové body:

  • virtual typed : každá odvozená Equalstřída přepíše tuto metodu pro rozšíření porovnání s vlastními poli.
  • GetType() guard: GetType() == other.GetType() zabraňuje Circle tomu, aby se rovnal Shape stejné barvě, a zabraňuje objektům různých odvozených typů, aby se navzájem rovnaly.
  • GetHashCode zahrnuje GetType(): protože dva objekty jsou stejné pouze v případě, že se jejich typy modulu runtime shodují, GetHashCode musí hashovat typ modulu runtime a také datová pole. Vynechání GetType() zde způsobí nesprávné chování a Dictionary<TKey,TValue>HashSet<T>.
  • == delegáty na Equals: udržuje operátor a rovnost metod konzistentní.

Implementace odvozené třídy

Odvozená třída, která přidává pole přepíše typ Equals, přetypuje na svůj vlastní typ, volání base.Equalsa pak porovná své vlastní pole:

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) vynucuje GetType() ochranu a kontroluje sdílená pole. Přetypování other is Circle c selže rychle, pokud je argumentem Shape jakýkoli jiný odvozený typ.

Použití prostřednictvím proměnné základního typu

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)

Zapečetěné třídy jsou jednodušší

Třídu nemůžete podtřídět sealed , takže typy runtime a čas kompilace vždy souhlasí. Nepotřebujete stráže GetType() ani virtual dispečink. Vzor IEquatable<T> zobrazený v implementaci rovnosti sami, když typ nemůže být záznam správný a dokončený pro zapečetěnou třídu.

Přetížení operátoru

Operátory a operátory == můžete != v uživatelem definovaném typu. Pokud přetížíte jeden z těchto dvou operátorů, musíte také přetížit druhý operátor.

Operátory a == operátory != v typu záznamu nelze explicitně přetížit. Chcete-li změnit chování a == operátory != pro typ Tzáznamu , implementujte metodu IEquatable<T>.Equals s následujícím podpisem:

public virtual bool Equals(T? other);

specifikace jazyka C#

Další informace najdete v části Relační a typové operátory specifikace jazyka C#.

Další informace o rovnosti typů záznamů najdete v části Členové rovnostispecifikace jazyka C#.

Viz také