Porovnání rovnosti v jazyce C#

Tip

Tento článek je součástí části Základy pro vývojáře, kteří už znají aspoň jeden programovací jazyk a učí se jazyk C#. Pokud s programováním teprve začínáte, začněte nejprve kurzy Začínáme .

Pochází z jiného jazyka? V Javě == a v JavaScriptu === testují u objektů identitu, nikoli obsah. Třídy jazyka C# fungují ve výchozím nastavení stejným způsobem. V Pythonu == volá __eq__ a ve výchozím nastavení porovnává obsah, podobně jako se porovnávají záznamy v C#. Struktury jazyka C# se také porovnávají podle hodnoty při volání Equals.

Jazyk C# rozlišuje dva druhy rovnosti. Rovnost hodnot znamená, že dvě instance jsou stejné, když se jejich data shodují. Rovnost odkazu znamená, že dvě proměnné jsou stejné pouze v okamžiku, kdy odkazují na stejný objekt v paměti. Tato podmínka se také nazývá identita. Typy hodnot obvykle porovnávají data a referenční typy obvykle porovnávají identitu. Autoři typů můžou tyto výchozí hodnoty změnit, ale tento mentální model zabraňuje drobným chybám, kdy dva objekty, které vypadají stejně, nejsou považovány za stejné nebo kde se mítnutí pomocí jedné proměnné tiše změní, co vidí jiná proměnná.

Typy hodnot, referenční typy a výchozí nastavení rovnosti

Každý typ v jazyce C# je typ hodnoty nebo typ odkazu. Typ hodnoty uchovává data přímo v proměnné. Typ odkazu obsahuje odkaz na objekt. Když přiřadíte proměnnou typu odkaz na jinou proměnnou, obě proměnné odkazují na stejný objekt. Další informace o typech hodnot a odkazových typech naleznete v tématu Přehled systému typů.

Výchozí chování rovnosti se obvykle řídí typem:

  • Předdefinované číselné typy a výčty jsou typy hodnot. Dvě int proměnné jsou stejné, když se jejich číselné hodnoty shodují.
  • Struktury jsou typy hodnot. Prostý struct používá rovnost hodnot při volání Equals.
  • Tice jsou hodnotové typy. Dvě n-tice jsou si rovny, pokud se shodují všechny hodnoty jejich prvků.
  • Třídy jsou odkazové typy. Obyčejná třída používá porovnávání podle reference, takže == a Equals testují, zda dvě proměnné ukazují na stejný objekt.

Třída používá porovnávání referencí. Dva samostatné objekty se stejnými daty se nerovnají, ale dvě proměnné, které odkazují na stejný objekt, jsou stejné:

var order1 = new Order(42, "Shoes");
var order2 = new Order(42, "Shoes");

Console.WriteLine(order1 == order2);               // => False
Console.WriteLine(order1.Equals(order2));          // => False
Console.WriteLine(ReferenceEquals(order1, order2)); // => False

Order order3 = order1;
Console.WriteLine(order1 == order3);               // => True

A struct zobrazuje rovnost hodnot prostřednictvím Equals. Dvě instance struktury jsou stejné, když se jejich pole shodují:

var pt1 = new Point(3, 4);
var pt2 = new Point(3, 4);

Console.WriteLine(pt1.Equals(pt2)); // => True

Prosté struktury nemají předdefinovaný operátor ==. Zápis p1 == p2 do prosté struktury se zkompiluje pouze v případě, že struktura deklaruje své vlastní operator ==. Pokud pro strukturu potřebujete relační operátory, definujte == a != jako dvojici a zachovejte jejich konzistenci s Equals a GetHashCode.

Ntice jsou také hodnotové typy. Dvě n-tice jsou si rovny, pokud se shoduje hodnota každého prvku. Názvy prvků v pojmenované n-tici slouží pouze pro pohodlí při kompilaci a při porovnávání se neberou v úvahu. Záleží jenom na pozicích a hodnotách:

var t1 = (Name: "Grace", Role: "Engineer");
var t2 = (Name: "Grace", Role: "Engineer");

Console.WriteLine(t1 == t2); // => True

Další informace o syntaxi řazených kolekcí členů a dekonstrukci naleznete v tématu Řazené kolekce členů a dekonstrukce.

Použití Object.ReferenceEquals k přímému otestování identity

ReferenceEquals vždy testuje identitu bez ohledu na to, jakým způsobem typ přepíše Equals nebo přetíží ==. Použijte ji jako diagnostiku identity, když potřebujete ověřit, jestli dvě proměnné odkazují na stejný objekt:

var doc1 = new Document("Report");
var doc2 = new Document("Report");
var doc3 = doc1;

Console.WriteLine(ReferenceEquals(doc1, doc2)); // => False
Console.WriteLine(ReferenceEquals(doc1, doc3)); // => True

Běžné použití je uvnitř přepsání metody Equals, aby se úplné porovnání mohlo přeskočit: pokud oba argumenty odkazují na stejnou instanci, jsou vždy považovány za rovné, aniž by bylo nutné kontrolovat jednotlivá pole.

Note

Pokud jsou proměnné zadány jako rozhraní, zkontroluje, == zda proměnné rozhraní odkazují na stejný objekt. Volání Equals stále spouští implementaci základního objektu.

Note

ReferenceEquals Vždy se vrátí false při porovnávání hodnotových typů, i když oba argumenty obsahují stejné hodnoty. K tomuto chování dochází, protože každý argument typu hodnoty je při předání metodě ReferenceEquals nezávisle zabalen do samostatného objektu v haldě.

Typy mohou definovat různé sémantiky rovnosti.

Typy mohou definovat sémantiku rovnosti, která se liší od výchozího chování. Nejběžnějším důvodem je implementace rovnosti hodnot. Pokud vytvoříte typ, který představuje data, jako je bankovní účet, produkt v inventáři nebo uživatel v systému, zvažte instance se stejnými hodnotami jako stejné. Zvolte typy záznamů pro implementaci rovnosti hodnot a kompilátor vygeneruje všechny potřebné členy rovnosti za vás.

Note

Řetězce jsou třídy, ale ==Equals porovnávají obsah řetězců, nikoli identitu.

  • ==: operátor rovnosti. Většina typů používá tento operátor jako primární kontrolu rovnosti. Jeho chování závisí na tom, jestli má typ integrovaný nebo uživatelem definovaný == operátor.
  • !=: operátor nerovnosti. Pokud typ definuje uživatelem definovaný == operátor, musí také definovat !=.
  • Equals: virtuální metoda děděná všemi typy. Můžete ji předefinovat a tím změnit sémantiku rovnosti pro daný typ.
  • GetHashCode: virtuální metoda používaná kolekcemi založenými na hashování. Pokud jsou dvě hodnoty stejné, musí být jejich kódy hash také stejné.
  • ReferenceEquals: statická metoda, která vždy testuje identitu.

Použití záznamů pro rovnost hodnot

Pomocí modifikátoru record určíte, že typ zaměřený na data má rovnost podle hodnoty, pokud může být záznamem. Kompilátor generuje Equals, GetHashCodea ==/!= členy, které porovnávají každou deklarovanou hodnotu vlastnosti.

A record class je stále referenčním typem, ale porovnává hodnoty místo identity:

var person1 = new Person("Ada", "Lovelace");
var person2 = new Person("Ada", "Lovelace");

Console.WriteLine(person1 == person2);               // => True
Console.WriteLine(person1.Equals(person2));          // => True
Console.WriteLine(ReferenceEquals(person1, person2)); // => False

ReferenceEquals potvrzuje, že person1 a person2 jsou různé objekty v paměti, zatímco == a Equals vracejí True, protože operátor rovnosti generovaný kompilátorem porovnává hodnoty vlastností.

Stejné generování kompilátoru platí pro record struct typy:

var dim1 = new Dimension(1920, 1080);
var dim2 = new Dimension(1920, 1080);

Console.WriteLine(dim1 == dim2);      // => True
Console.WriteLine(dim1.Equals(dim2)); // => True

Typy záznamů generují celou sadu rovnosti pro svůj vlastní typ. Typy record class i record struct přepisují Equals a GetHashCode. Také generují == a != operátory a také typovanou Equals metodu pro typ záznamu. Na rozdíl od prostého struct, proto record struct podporuje == a != automaticky. Další informace o typech záznamů a jejich sémantice rovnosti naleznete v tématu Záznamy.

Záznamy se členy referenčního typu

Rovnost záznamů používá vlastní sémantiku rovnosti členů. Každá vlastnost nebo pole se porovnává pomocí vlastní Equals metody. U většiny skalárních hodnot, například int, stringnebo DateTime, tento přístup porovnává hodnoty členů záznamů. Nuance nastává u běžných proměnlivých kolekcí, jako jsou List<T> nebo T[]: tyto typy se porovnávají podle reference, takže dvě instance recordů, které obsahují různé objekty typu seznam se stejným obsahem, nejsou podle syntetizované rovnosti recordů považovány za stejné.

var playlist1 = new Playlist("Chill", new List<string> { "Song A", "Song B" });
var playlist2 = new Playlist("Chill", new List<string> { "Song A", "Song B" });

Console.WriteLine(playlist1.Equals(playlist2));                        // => False (different List instances)
Console.WriteLine(playlist1.Tracks.SequenceEqual(playlist2.Tracks));   // => True

playlist1 a playlist2 jsou samostatné List<string> instance. I když se jejich obsah shoduje, Equals vrátí false.

Pokud potřebujete, aby se v obsahu kolekce projevila rovnost záznamů, máte několik možností:

  • Implementujte IEquatable<T> v záznamu a přepište Equals, aby se pro členy kolekce použilo Enumerable.SequenceEqual.
  • Použijte typ kolekce s porovnáváním podle hodnoty – například vlastní IEqualityComparer<T> nebo typ, jehož vlastní Equals porovnává prvky.
  • Návrh na identitu: Pokud záznam představuje entitu místo čisté hodnoty, může být rovnost odkazů pro členy kolekce úmyslná.

Important

Ruční implementace rovnosti je dnes v jazyce C# vzácná. Záznamy zpracovávají běžný scénář rovnosti hodnot automaticky. Pokud potřebujete implementovat rovnost ručně – například protože váš typ musí být odvozen z nezáznamové základní třídy – podívejte se do referenčních informací k jazyku na článek Implementace rovnosti, pokud typ nemůže být záznamem.

Viz také