C# Egyenlőségi összehasonlítások

Tip

Ez a cikk az Alapismeretek szakasz része azon fejlesztők számára, akik már legalább egy programozási nyelvet ismernek, és c#-t tanulnak. Ha még csak most ismerkedik a programozással, először az Első lépések oktatóanyagokkal kezdje.

Más nyelvről jön? A Java nyelvben az objektumok esetén a ==, a JavaScriptben pedig a === az azonosságot ellenőrzi, nem pedig a tartalmat. A C#-osztályok alapértelmezés szerint ugyanúgy működnek. A Python == a C#-rekordok összehasonlításához hasonlóan alapértelmezés szerint meghívja __eq__ és teszteli a tartalmat. A C# struktúrák is érték szerint hasonlítódnak össze, amikor meghívja a Equals elemet.

A C# kétféle egyenlőséget különböztet meg. Az értékegyenlőség azt jelenti, hogy két példány akkor egyenlő, ha az adataik megegyeznek. A hivatkozási egyenlőség azt jelenti, hogy két változó csak akkor egyenlő, ha ugyanarra az objektumra mutatnak a memóriában. Ezt a feltételt identitásnak is nevezik. Az értéktípusok általában az adatokat hasonlítják össze, a referenciatípusok pedig általában az identitást. A típusszerzők módosíthatják ezeket az alapértelmezett értékeket, de ez a mentális modell megakadályozza a finom hibákat, ha két azonosnak tűnő objektum nem tekinthető egyenlőnek, vagy ha az egyik változón keresztüli mutáció csendesen megváltoztatja a másik változó által látottakat.

Értéktípusok, referenciatípusok és egyenlőség alapértelmezett értékei

A C# minden típusa értéktípus vagy referenciatípus. Egy értéktípus közvetlenül a változóban tárolja az adatait. A referenciatípus egy objektumra mutató hivatkozást tartalmaz. Ha egy referencia típusú változót egy másik változóhoz rendel, mindkét változó ugyanarra az objektumra hivatkozik. Az értéktípusokról és a referenciatípusokról további információt a Típusrendszer áttekintése című témakörben talál.

Az alapértelmezett egyenlőségi viselkedés általában a típust követi:

  • A beépített numerikus típusok és enumerálások értéktípusok. Két int változó egyenlő, ha a numerikus értékek egyeznek.
  • A szerkezetek értéktípusok. Egy sima struct érték szerinti egyenlőséget használ, amikor meghívod a Equals-t.
  • A tuple-ök értéktípusok. Két n-es egyenlő, ha minden elemük értéke megegyezik.
  • Az osztályok referenciatípusok. Az egyszerű osztály hivatkozási egyenlőséget használ, így ==Equals teszteli, hogy két változó ugyanarra az objektumra mutat-e.

Az osztály hivatkozási egyenlőséget használ. Két azonos adattal rendelkező objektum nem egyenlő, de két azonos objektumra hivatkozó változó egyenlő:

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 az érték szerinti egyenlőséget a Equals segítségével mutatja. Két structpéldány egyenlő, ha a mezőik megegyeznek:

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

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

Az egyszerű szerkezetek nem kapnak előre definiált operátort == . A p1 == p2 használata egy egyszerű struktúrán csak akkor fordul le, ha a struktúra saját operator ==-t deklarál. Ha egy struktúrához összehasonlító operátorokra van szüksége, definiálja a(z) == és != operátorokat párban, és tartsa őket összhangban a(z) Equals és GetHashCode operátorokkal.

A tuple-ök is értéktípusok. Két n-es akkor egyenlő, ha minden elem értéke megegyezik. Az elnevezett tuple elemeinek nevei csak a fordításkori kényelmet szolgálják, és az összehasonlításkor nem veszik őket figyelembe. Csak a pozíciók és értékek számítanak:

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

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

A tuple-szintaxissal és a dekonstruálással kapcsolatos további információkért lásd a Tuples and deconstruction című témakört.

Az identitás közvetlen tesztelésére használható Object.ReferenceEquals

ReferenceEquals mindig az azonosságot vizsgálja, függetlenül attól, hogy egy típus hogyan írja felül a Equals elemet, illetve hogyan terheli túl a == elemet. Használja identitásdiagnosztikaként, ha meg kell győződnie arról, hogy két változó pontosan ugyanarra az objektumra mutat:

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

Gyakran használják egy Equals felülírásán belül a teljes összehasonlítás lerövidítésére: ha mindkét argumentum ugyanarra az objektumra mutat, akkor az egyes mezők ellenőrzése nélkül mindig egyenlőnek számítanak.

Note

Ha a változók interfészként vannak deklarálva, a == ellenőrzi, hogy az interfész típusú változók ugyanarra az objektumra hivatkoznak-e. A(z) Equals hívása továbbra is a mögöttes objektum implementációját futtatja.

Note

ReferenceEquals mindig az értéktípusok összehasonlításakor ad vissza false értéket, még akkor is, ha mindkét argumentum ugyanazokat az értékeket tartalmazza. Ez a viselkedés azért fordul elő, mert minden egyes értéktípusú argumentum egymástól függetlenül egy külön kupacobjektumba lesz dobozolva, amikor a(z) ReferenceEquals paramétereként kerül átadásra.

A típusok különböző egyenlőségi szemantikát definiálhatnak

A típusok az alapértelmezett viselkedéstől eltérő egyenlőségi szemantikát definiálhatnak . A leggyakoribb ok az értékegyenlőség megvalósítása. Ha olyan típust hoz létre, amely adatokat jelöl, például bankszámlát, készletben lévő terméket vagy rendszerbeli felhasználót, vegye figyelembe az egyenlő értékekkel rendelkező példányokat. Válasszon rekordtípusokat az értékegyenlőség megvalósításához, és a fordító létrehozza az összes szükséges egyenlőségi tagot.

Note

A sztringek osztályok, de == és Equals a sztringek tartalmát hasonlítják össze, nem az azonosságukat.

  • ==: az egyenlőségi operátor. A legtöbb típus ezt az operátort használja elsődleges egyenlőség-ellenőrzésként. Viselkedése attól függ, hogy a típus rendelkezik-e beépített vagy felhasználó által definiált == operátorral.
  • !=: az egyenlőtlenség operátora. Ha egy típus felhasználó által definiált == operátort határoz meg, akkor azt is meg kell határoznia !=.
  • Equals: minden típus által öröklődő virtuális metódus. Felülbírálhatja a típus egyenlőségi szemantikájának módosításához.
  • GetHashCode: kivonatalapú gyűjtemények által használt virtuális módszer. Ha két érték egyenlő, a kivonatkódjaiknak is egyenlőnek kell lenniük.
  • ReferenceEquals: egy statikus módszer, amely mindig teszteli az identitást.

Rekordok használata értékegyenlőséghez

record A módosító használatával adatközpontú típusérték-egyenlőséget adhat, ha a típus rekord lehet. A fordító olyan tagokat Equalshoz létre, GetHashCode==/!= amelyek minden deklarált tulajdonságértéket összehasonlítanak.

Az A record class továbbra is referenciatípus, de identitás helyett értékeket hasonlít össze:

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 megerősíti, hogy person1 és person2 különböző objektumok a memóriában, míg == és EqualsTrue értéket ad vissza, mert a fordító által generált egyenlőségvizsgálat a tulajdonságértékeket hasonlítja össze.

Ugyanez a fordítógeneráció a következő típusokra record struct vonatkozik:

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

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

A rekordtípusok a teljes egyenlőségi csoportot a saját típusukhoz állítják elő. Mind a record class, mind a record struct típus felülírja a Equals és a GetHashCode elemet. Emellett létrehoznak == és != operátorokat is, valamint egy gépelt Equals metódust a rekordtípushoz. A sima struct-val ellentétben a record struct ezért automatikusan támogatja a == és a != használatát. A rekordtípusokról és azok egyenlőségi szemantikáról további információt a Rekordok című témakörben talál.

Referencia típusú tagokat tartalmazó rekordok

A rekordegyenlőség a tagok egyenlőségi szemantikáját használja. Az egyes tulajdonságokat vagy mezőket a rendszer a saját Equals módszerével hasonlítja össze. A legtöbb skaláris érték, például int, stringvagy DateTime, ez a módszer összehasonlítja a rekordtagok értékeit. A nehézség az olyan gyakori módosítható gyűjteményeknél merül fel, mint például a List<T> vagy a T[]: ezeket a típusokat hivatkozás szerint hasonlítják össze, így két olyan rekordpéldány, amely azonos tartalmú, de különböző listaobjektumokat tartalmaz, a szintetizált rekordegyenlőség szerint nem minősül egyenlőnek.

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 és playlist2 különálló List<string> példányok. Annak ellenére, hogy a tartalmuk megegyezik, a(z) Equals a(z) false értéket adja vissza.

Ha rekordegyenlőségre van szüksége a gyűjtemény tartalmának tükrözéséhez, a következő lehetőségek közül választhat:

  • Implementálja IEquatable<T> a rekordon, és bírálja felül a(z) Equals elemet, hogy a gyűjtemény tagjaihoz a(z) Enumerable.SequenceEqual elemet használja.
  • Gyűjteménytípus használata értékegyenlőséggel – például egyéni IEqualityComparer<T> vagy olyan típus, amelynek saját Equals elemeit hasonlítja össze.
  • Tervezés az identitás körül: ha a rekord egy entitást jelöl, nem pedig egy tiszta értéket, a gyűjteménytagok egyenlőségére mutató hivatkozás szándékos lehet.

Important

Az egyenlőség manuális megvalósítása napjainkban ritkán fordul elő a C#-ban. A rekordok automatikusan kezelik az értékegyenlőség gyakori forgatókönyvét. Ha manuálisan kell implementálnia az egyenlőséget – például mert a típusnak egy nem rekordalapú alaposztályból kell származnia –, olvassa el az Egyenlőség megvalósítása saját maga című témakört, ha egy típus nem lehet rekord a nyelvi referenciaban.

Lásd még