Kurzusok
/
C#
C#
/
Tanterv
/
Egyenlőség
Eg

Egyenlőség ebben a kurzusban: C#

5 feladat

A(z) Egyenlőség fogalomról

A feladat az egyenlőség számos tulajdonságát szemlélteti a C#-ban:

Object.Equals()

A feladat által érintett témák

  • Az egyszerű típusokat (stringeket és primitíveket) rendszerint a == és a != operátorral vizsgáljuk egyenlőség szempontjából. Ez idiomatikusabbnak számít, mint az Equals() metódus használata, amely ezeknél a típusoknál szintén elérhető. A Java-programozók legyenek éberek: amikor stringekkel dolgoznak, tudniuk kell, hogy a == a C#-ban érték szerint hasonlít össze, Javában viszont referenciák szerint, amikor visszatérnek korábbi nyelvükhöz.
  • A referenciatípusokat (osztályok példányait) az object-től örökölt Equals() metódussal hasonlítjuk össze. Ha az egyenlőségvizsgálat célja annak biztosítása, hogy két objektum pontosan ugyanaz a példány, akkor az object implementációjára hagyatkozva is elegendő. Ha nem, felül kell írnod az object.Equals() metódust.
  • Ha tudod, hogy az osztályod minden példánya egy helyen jön létre, mondjuk egy játék vagy szimuláció szereplői, akkor a referencia szerinti egyenlőség is elegendő. Valószínű azonban, hogy ugyanannak a valós entitásnak több példánya is létrejön (például egy adatbázisból, felhasználói bevitelből vagy egy webes kérésen keresztül). Ilyenkor az entitást egyedileg azonosító értékeket kell egyenlőség szempontjából vizsgálni. Ezért az Equals() metódust felül kell írni.
  • Egy felülírt Equals() az egyszerű típusú tagokon ==-s egyenlőségvizsgálatokat, a referenciatípusokon pedig rekurzív Equals()-hívásokat fog tartalmazni.
class StatusBar
{
    private readonly int width = 200, height = 20;

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return width == (other as StatusBar).width && height == (other as StatusBar).height;
    }
}
class Window
{
    private readonly string title = "Main";
    private readonly StatusBar statusBar = new StatusBar();

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
    }
}

  • A statikus object.ReferenceEquals() metódussal hasonlítható össze két objektum annak megállapítására, hogy ugyanaz-e a példány. Ez áttekinthetőbbé teszi a kódot, és elengedhetetlen ott, ahol az Equals() és a == már felül van írva vagy túl van terhelve.
var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true

Kiegészítő témák

  • A public override bool Equals(object obj) mellett az IDE-k általában legenerálják a protected bool Equals(FacialFeatures other) túlterhelést is, amely öröklődés esetén használható. A leszármazott osztály meghívhatja az ősosztály Equals() metódusát, majd hozzáadhatja a saját vizsgálatát.
  • Ne használd a == operátort, hacsak nem túlterhelted az == operátort és az Equals() metódust is az osztályodban (lásd az operator-overloading feladatot), vagy hacsak nem csak az számít, hogy a referenciák egyenlők.
  • A struct-ok egyenlőségvizsgálatával a structs feladat foglalkozik.
  • A tuple-ök egyenlőségével a tuples feladat foglalkozik.
  • Sok fejlesztő az IDE-jére bízza az egyenlőségmetódusok implementálását, mivel azok az egyenlőség minden apró részletére odafigyelnek. Például a JetBrains RIDER (2020.1-es verzió) a következő egyenlőségmetódusokat generálja egy osztályhoz:
protected bool Equals(T other)
{
    return field1 == other.field1 && field2.Equals(other.field2);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((T) obj);
}
  • Légy óvatos, ha úgy döntesz, hogy javítani szeretnél az IDE által generált kódon. Az általában golyóálló a null értékekkel és az elgépelt típusú objektumokkal szemben, és meglehetősen jól optimalizált.
  • A delegáltak egyenlőségének vizsgálatát a feladat nem tárgyalja külön.
  • A tömbökhöz és a legtöbb gyűjteményhez nincs beépített egyenlőségvizsgálat. A LINQ (amelyet későbbi feladatokban tárgyalunk) biztosít SequenceEquals() metódust, LINQ nélkül viszont mindkét gyűjteményen végig kell iterálni, és az elemeket egyenként összehasonlítani.
  • Arról, hogyan használd a == és != operátort a saját osztályaiddal, a operator-overloading feladatban olvashatsz.

Object.GetHashCode()

  • Az object.GetHashCode() egy 32 bites egész szám formájában ad vissza egy hash-kódot. A hash-kódot a szótár- és halmazosztályok, például a Dictionary<T> és a HashSet<T> használják arra, hogy hatékonyan tároljanak és kérdezzenek le objektumokat. Szótárak esetében a hash-elés a kulcsokhoz kapcsolódik.
  • A C#-fejlesztők körében elvárás, hogy ha felülírod az Equals() metódust, akkor a GetHashCode() metódust is felülírd. Az Equals() és a GetHashCode() között van egy kapcsolat, amelynek teljesülnie kell ahhoz, hogy a szótár- és halmazosztályok, valamint minden más hash-kódot használó osztály helyesen működjön. Elvárás, hogy úgy implementáld a metódust, hogy ne állíts csapdát azoknak a karbantartóknak, akik később esetleg egy hash-kód alapú gyűjteményt adnak hozzá.
  • A hash-kód és az egyenlőség kapcsolata a következő: ha két objektum egyenlő (Equal() igazat ad vissza), akkor a két objektum GetHashCode() metódusának ugyanazt az értéket kell visszaadnia. Ez fordítva nem igaz. A kapcsolat nem szimmetrikus. Képzelj el egy keresőfüggvényt, amely először a hash-kód alapján egy „bucketbe” megy, majd az egyenlőségvizsgálat segítségével kiválasztja az adott elemet.
  • A hash-kód létrehozásának legegyszerűbb módja a HashCode.Combine() meghívása, átadva neki az egyenlőségvizsgálatban használt értékeket (vagy azok egy részhalmazát). Tartsd észben: minél több információt adsz át a Combine() metódusnak, annál hatékonyabb lesz várhatóan a hash-implementáció.
public class Assessment
{
    private int rating;
    private Person boss;

    public override int GetHashCode()
    {
        return HashCode.Combine(rating, boss);
    }
}
  • Az egyenlőségvizsgálatban használt értékeknek stabilnak kell maradniuk, amíg a hash-elt gyűjtemény használatban van. Ha egy objektumot az egyik értékkészlettel adsz hozzá a gyűjteményhez, majd megváltoztatod ezeket az értékeket, a hash-kód már nem a megfelelő „bucketre” fog mutatni. A gyakorlatban ez azt jelenti, hogy az objektumnak módosíthatatlannak kell lennie. Más megközelítések kockázatot jelentenek, mert buktatókat teremthetnek a karbantartóknak. A módosíthatatlanságot más feladatok tárgyalják.
  • Lehetséges, hogy jobb hash-kódot tudsz tervezni, mint amit a könyvtári rutinok előállítanak, de ez vagy azért van, mert részletesen ismered az adatok jellemzőit, vagy azért, mert a gyűjtemény nagyon egyszerű, és az értékek hash-elés nélkül közvetlenül használhatók. Lehet, hogy nem éri meg a plusz erőfeszítést.

Teljesítményjavítások

A teljesítmény kismértékű javítása érdekében, különösen amikor az objektumok gyűjteményekhez tartoznak, hozzáadhatsz egy túlterhelt tagot: public bool Equals(T other).

Ez bizonyos mennyiségű típusellenőrzést takarít meg a referenciatípusoknál, az értéktípusoknál pedig egy boxolási lépést, mivel nem kell objektummá alakítani (boxolni) őket a public override bool Equals(object other) argumentumaként.

Ha hozzáadod az IEquatable<T> interfészt az osztályodhoz, az megköveteli a túlterhelés implementálását. Hacsak a kódod nem tartalmaz olyan rutinokat, amelyek IEquatable<T> típusú objektumokat fogadnak (és feltehetően valami érdekeset tesznek az egyenlőséggel kapcsolatban, függetlenül az implementáló osztálytól), valójában nincs más nyomós ok az interfész felvételére.

IEqualityComparer<T>

Ha van egy osztályod, amely két különböző módon is egyedileg azonosítható, mondjuk egy Person osztály, amely egy SSID-vel és egy egyedi e-mail-címmel azonosítható, akkor a .NET eszközt nyújt arra, hogy két különböző gyűjtemény különböző hash-kód- és egyenlőségvizsgálatot használhasson. Mindkettőnek lehet saját, eltérő IEqualityComparer<T> implementációja, saját Equals() és GetHashCode() metódussal. Lehet egy szótárad SSID-ra és egy másik e-mail-címre kulcsolva.

Amikor IEqualityComparer<T> van használatban, az elemeket reprezentáló osztályon általában továbbra is felülírod az Equals() és a GetHashCode() metódust, hogy elkerüld a gyűjteményosztályokon kívüli problémákat.

Az IEqualityComparer<T> használatakor az egyik szempont, hogy a privát metódusok és hasonlók nem lesznek elérhetők az egyenlőségvizsgálat számára.

Ha csak egy hash-elt gyűjtemény van használatban, akkor jobb lehet elkerülni az IEqualityComparer<T> használatát, és az egyenlőséget meg a hash-kódot magában az objektumban elhelyezni. Eleve nem ideális, hogy egy olyan kulcsfontosságú függőséget, mint amilyen az objektum és a hash-elt gyűjtemény között van, a fordító nem tud kikényszeríteni, de ha a hash-elési és összehasonlítási logika máshol van, akkor a karbantartónak még könnyebb lenne úgy módosítania egy osztály tagjait, hogy nem törődik a gyűjteményre gyakorolt következményekkel.

Megjegyzés a lebegőpontos egyenlőségről

Az egyik primitív, amely próbára teheti a gyanútlan programozót, a lebegőpontos értékek egyenlőségének vizsgálata. Ezt a floating-point-numbers fogalom about.md dokumentuma tárgyalja.

Egyenlőség és öröklődés

Ez a cikk bemutat néhány döntést, amelyet az egyenlőséggel és az öröklődéssel kapcsolatban meg kell hozni. Ezek csak akkor érintenek, ha egy osztály és egy leszármaztatott osztály példányaival dolgozol, és a kettő közötti egyenlőséget kell vizsgálnod.

Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg

Tanuld meg a(z) Egyenlőség fogalmat

A gyakorlás zárolva

Oldj fel még 4 feladatot, hogy gyakorolhasd a(z) Egyenlőség fogalmat