Megosztás a következőn keresztül:


Értékegyenlőség definiálása egy osztályhoz vagy szerkezethez (C# programozási útmutató)

A rekordok automatikusan implementálják az értékegyenlőséget. Amikor a típus adattípusokat alkalmaz, érdemes lehet definiálni a record ahelyett, hogy a class adatokat, és meg kell valósítania az értékegyenlőséget.

Amikor osztályt vagy szerkezetet határoz meg, eldönti, hogy érdemes-e egyéni értékegyenlőséget (vagy egyenértékűséget) létrehozni a típushoz. Általában akkor valósítja meg az értékegyenlőséget, ha egy gyűjteményhez ilyen típusú objektumokat szeretne hozzáadni, vagy amikor elsődleges célja mezők vagy tulajdonságok készletének tárolása. Az értékegyenlőség definícióját a típus összes mezőjének és tulajdonságainak összehasonlítására alapozhatja, vagy a definíciót egy részhalmazra alapozhatja.

Mindkét esetben, és mindkét osztályban és szerkezetben a megvalósításnak az öt egyenértékűségi garanciát kell követnie (a következő szabályok esetében feltételezze, hogy x, y és z nem null értékűek):

  1. A reflexív tulajdonság: x.Equals(x) visszaadja true.

  2. A szimmetrikus tulajdonság: x.Equals(y) ugyanazt az értéket adja vissza, mint a y.Equals(x).

  3. A tranzitív tulajdonság: ha (x.Equals(y) && y.Equals(z)) visszaadja true-t, akkor x.Equals(z) visszaadja true-t.

  4. Az egymást követő meghívások x.Equals(y) ugyanazt az értéket adják vissza, amíg az x és az y által hivatkozott objektumok nem módosulnak.

  5. A nem null értékű értékek nem egyenlők a null értékkel. Null x.Equals(y) érték esetén x azonban kivételt eredményez. Ez az 1-es vagy a 2-es szabályt sérti, attól függően, hogy mi az argumentum a Equals számára.

Az Ön által definiált bármely struktúra már rendelkezik az értékegyenlőség alapértelmezett implementációjával, amelyet a System.ValueType metódus Object.Equals(Object) felülbírálásából örököl. Ez az implementáció tükröződés segítségével vizsgálja meg a típus összes mezőjét és tulajdonságát. Bár ez az implementáció helyes eredményeket hoz létre, viszonylag lassú a kifejezetten a típushoz írt egyéni implementációhoz képest.

Az értékegyenlőség implementálási részletei az osztályok és a szerkezetek esetében eltérőek. Az osztályok és a szerkezetek azonban ugyanazokat az alapvető lépéseket igénylik az egyenlőség megvalósításához:

  1. Bírálja felül a virtuálisObject.Equals(Object) metódust. A legtöbb esetben az ön bool Equals( object obj ) implementációjának csak a Equals felület megvalósítását végző, típusspecifikus System.IEquatable<T> metódust kell meghívnia. (Lásd a 2. lépést.)

  2. Implementálja az System.IEquatable<T> interfészt egy típusspecifikus Equals metódus megadásával. Itt történik a tényleges egyenértékűség-összehasonlítás. Dönthet például úgy, hogy egyenlőséget határoz meg, ha csak egy vagy két mezőt hasonlít össze a típusában. Ne dobjon kivételeket a kódrészletből Equals. Örökléssel összefüggő osztályok esetén:

    • Ennek a módszernek csak az osztályban deklarált mezőket kell megvizsgálnia. Meg kell hívnia base.Equals , hogy vizsgálja meg az alaposztályban lévő mezőket. (Ne hívja meg base.Equals, ha a típus közvetlenül örököl Object, mert a Object végrehajtása referenciális egyenlőség-ellenőrzést hajt végre Object.Equals(Object).)

    • Két változó csak akkor tekinthető egyenlőnek, ha az összehasonlítandó változók futásidejű típusai megegyeznek. Győződjön meg arról is, hogy a IEquatableEquals futási idő típusának metódusa akkor lesz implementálva, ha egy változó futásideje és fordítási ideje eltérő. Az egyik stratégia annak biztosítására, hogy a futásidejű típusok mindig helyesen legyenek összehasonlítva, az, hogy a IEquatable osztályokban csak a sealed kerüljön implementálásra. További információkért tekintse meg a cikk későbbi részében található osztály példáját .

  3. Nem kötelező, de ajánlott: Túlterhelje a == és a != operátorokat.

  4. Bíráld felül a Object.GetHashCode elemet, hogy két, értékegyenlőséggel rendelkező objektum ugyanazt a kivonatkódot hozzon létre.

  5. Nem kötelező: A "nagyobb, mint" vagy a "kisebb, mint" definíciók támogatásához implementálja a IComparable<T> interfészt a típusához, és túlterhelje az <= és az >= operátorokat is.

Megjegyzés

A rekordok használatával felesleges sablonkód nélkül is lekérheti az értékegyenlőség szemantikáját.

Osztály példa

Az alábbi példa bemutatja, hogyan valósíthat meg értékegyenlőséget egy osztályban (referenciatípus).

namespace ValueEqualityClass;

class TwoDPoint : IEquatable<TwoDPoint>
{
    public int X { get; private set; }
    public int Y { get; private set; }

    public TwoDPoint(int x, int y)
    {
        if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
        {
            throw new ArgumentException("Point must be in range 1 - 2000");
        }
        this.X = x;
        this.Y = y;
    }

    public override bool Equals(object obj) => this.Equals(obj as TwoDPoint);

    public bool Equals(TwoDPoint p)
    {
        if (p is null)
        {
            return false;
        }

        // Optimization for a common success case.
        if (Object.ReferenceEquals(this, p))
        {
            return true;
        }

        // If run-time types are not exactly the same, return false.
        if (this.GetType() != p.GetType())
        {
            return false;
        }

        // Return true if the fields match.
        // Note that the base class is not invoked because it is
        // System.Object, which defines Equals as reference equality.
        return (X == p.X) && (Y == p.Y);
    }

    public override int GetHashCode() => (X, Y).GetHashCode();

    public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
    {
        if (lhs is null)
        {
            if (rhs is null)
            {
                return true;
            }

            // Only the left side is null.
            return false;
        }
        // Equals handles case of null on right side.
        return lhs.Equals(rhs);
    }

    public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs == rhs);
}

// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.
class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
{
    public int Z { get; private set; }

    public ThreeDPoint(int x, int y, int z)
        : base(x, y)
    {
        if ((z < 1) || (z > 2000))
        {
            throw new ArgumentException("Point must be in range 1 - 2000");
        }
        this.Z = z;
    }

    public override bool Equals(object obj) => this.Equals(obj as ThreeDPoint);

    public bool Equals(ThreeDPoint p)
    {
        if (p is null)
        {
            return false;
        }

        // Optimization for a common success case.
        if (Object.ReferenceEquals(this, p))
        {
            return true;
        }

        // Check properties that this class declares.
        if (Z == p.Z)
        {
            // Let base class check its own fields
            // and do the run-time type comparison.
            return base.Equals((TwoDPoint)p);
        }
        else
        {
            return false;
        }
    }

    public override int GetHashCode() => (X, Y, Z).GetHashCode();

    public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
    {
        if (lhs is null)
        {
            if (rhs is null)
            {
                // null == null = true.
                return true;
            }

            // Only the left side is null.
            return false;
        }
        // Equals handles the case of null on right side.
        return lhs.Equals(rhs);
    }

    public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs) => !(lhs == rhs);
}

class Program
{
    static void Main(string[] args)
    {
        ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
        ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
        ThreeDPoint pointC = null;
        int i = 5;

        Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}");
        Console.WriteLine($"pointA == pointB = {pointA == pointB}");
        Console.WriteLine($"null comparison = {pointA.Equals(pointC)}");
        Console.WriteLine($"Compare to some other type = {pointA.Equals(i)}");

        TwoDPoint pointD = null;
        TwoDPoint pointE = null;

        Console.WriteLine($"Two null TwoDPoints are equal: {pointD == pointE}");

        pointE = new TwoDPoint(3, 4);
        Console.WriteLine($"(pointE == pointA) = {pointE == pointA}");
        Console.WriteLine($"(pointA == pointE) = {pointA == pointE}");
        Console.WriteLine($"(pointA != pointE) = {pointA != pointE}");

        System.Collections.ArrayList list = new System.Collections.ArrayList();
        list.Add(new ThreeDPoint(3, 4, 5));
        Console.WriteLine($"pointE.Equals(list[0]): {pointE.Equals(list[0])}");

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

/* Output:
    pointA.Equals(pointB) = True
    pointA == pointB = True
    null comparison = False
    Compare to some other type = False
    Two null TwoDPoints are equal: True
    (pointE == pointA) = False
    (pointA == pointE) = False
    (pointA != pointE) = True
    pointE.Equals(list[0]): False
*/

Az osztályok (referenciatípusok) esetében mindkét Object.Equals(Object) módszer alapértelmezett implementációja a referenciaegyenlítési összehasonlítást hajtja végre, nem értékegyenlőség-ellenőrzést. Ha egy implementáló felülírja a virtuális módszert, a cél az, hogy értéket adjon neki az egyenlőség szemantikájának.

Az == osztályokkal és != operátorokkal akkor is használható, ha az osztály nem túlterheli őket. Az alapértelmezett viselkedés azonban a referencia-egyenlőség ellenőrzése. Egy osztályban, ha túlterheli a Equals metódust, akkor érdemes lenne túlterhelni a == és != operátorokat is, de ez nem kötelező.

Fontos

Előfordulhat, hogy az előző példakód nem minden öröklési forgatókönyvet a várt módon kezel. Tekintse meg az alábbi kódot:

TwoDPoint p1 = new ThreeDPoint(1, 2, 3);
TwoDPoint p2 = new ThreeDPoint(1, 2, 4);
Console.WriteLine(p1.Equals(p2)); // output: True

Ez a kód azt jelzi, hogy p1 egyenlő p2-vel, annak ellenére, hogy különbség van a z értékek között. A különbség figyelmen kívül marad, mert a fordító a fordítási időbeli típus alapján választja ki a TwoDPoint megvalósítást IEquatable.

A típusok beépített értékegyenlősége record megfelelően kezeli az ehhez hasonló forgatókönyveket. Ha TwoDPoint és ThreeDPoint lenne record típusok, az eredmény az p1.Equals(p2) lenne False. További információ: Egyenlőség a típusöröklési hierarchiákbanrecord.

Példa strukturálásra

Az alábbi példa bemutatja, hogyan valósíthatja meg az értékegyenlőséget egy szerkezetben (értéktípus):

namespace ValueEqualityStruct
{
    struct TwoDPoint : IEquatable<TwoDPoint>
    {
        public int X { get; private set; }
        public int Y { get; private set; }

        public TwoDPoint(int x, int y)
            : this()
        {
            if (x is (< 1 or > 2000) || y is (< 1 or > 2000))
            {
                throw new ArgumentException("Point must be in range 1 - 2000");
            }
            X = x;
            Y = y;
        }

        public override bool Equals(object? obj) => obj is TwoDPoint other && this.Equals(other);

        public bool Equals(TwoDPoint p) => X == p.X && Y == p.Y;

        public override int GetHashCode() => (X, Y).GetHashCode();

        public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) => lhs.Equals(rhs);

        public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) => !(lhs == rhs);
    }

    class Program
    {
        static void Main(string[] args)
        {
            TwoDPoint pointA = new TwoDPoint(3, 4);
            TwoDPoint pointB = new TwoDPoint(3, 4);
            int i = 5;

            // True:
            Console.WriteLine($"pointA.Equals(pointB) = {pointA.Equals(pointB)}");
            // True:
            Console.WriteLine($"pointA == pointB = {pointA == pointB}");
            // True:
            Console.WriteLine($"object.Equals(pointA, pointB) = {object.Equals(pointA, pointB)}");
            // False:
            Console.WriteLine($"pointA.Equals(null) = {pointA.Equals(null)}");
            // False:
            Console.WriteLine($"(pointA == null) = {pointA == null}");
            // True:
            Console.WriteLine($"(pointA != null) = {pointA != null}");
            // False:
            Console.WriteLine($"pointA.Equals(i) = {pointA.Equals(i)}");
            // CS0019:
            // Console.WriteLine($"pointA == i = {pointA == i}");

            // Compare unboxed to boxed.
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            list.Add(new TwoDPoint(3, 4));
            // True:
            Console.WriteLine($"pointA.Equals(list[0]): {pointA.Equals(list[0])}");

            // Compare nullable to nullable and to non-nullable.
            TwoDPoint? pointC = null;
            TwoDPoint? pointD = null;
            // False:
            Console.WriteLine($"pointA == (pointC = null) = {pointA == pointC}");
            // True:
            Console.WriteLine($"pointC == pointD = {pointC == pointD}");

            TwoDPoint temp = new TwoDPoint(3, 4);
            pointC = temp;
            // True:
            Console.WriteLine($"pointA == (pointC = 3,4) = {pointA == pointC}");

            pointD = temp;
            // True:
            Console.WriteLine($"pointD == (pointC = 3,4) = {pointD == pointC}");

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }

    /* Output:
        pointA.Equals(pointB) = True
        pointA == pointB = True
        Object.Equals(pointA, pointB) = True
        pointA.Equals(null) = False
        (pointA == null) = False
        (pointA != null) = True
        pointA.Equals(i) = False
        pointE.Equals(list[0]): True
        pointA == (pointC = null) = False
        pointC == pointD = True
        pointA == (pointC = 3,4) = True
        pointD == (pointC = 3,4) = True
    */
}

A szerkezetek esetében az alapértelmezett implementáció Object.Equals(Object) (amely a System.ValueType felülírt verziója) reflektálást használ az értékek összehasonlítására, hogy ellenőrizze a típus minden mezőjének értékegyenlőségét. Ha egy implementáló felülbírálja a virtuális Equals metódust egy szerkezetben, a cél az értékegyenlőség-ellenőrzés hatékonyabb eszközének biztosítása, és opcionálisan az összehasonlítás alapja a szerkezet mezőinek vagy tulajdonságainak egy részhalmaza.

Az == és != operátorok csak akkor működhetnek a szerkezeten, ha a szerkezet explicit módon túlterheli őket.

Lásd még