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


Oktatóanyag: Statikus virtuális tagok felfedezése felületeken

Az interfész statikus virtuális tagjai lehetővé teszik a túlterhelt operátorokat vagy más statikus tagokat tartalmazó interfészek meghatározását. Miután definiálta a statikus tagokkal rendelkező illesztőket, ezeket az illesztőket korlátozásként használhatja az operátorokat vagy más statikus metódusokat használó általános típusok létrehozásához. Még ha nem is hoz létre túlterhelt operátorokkal rendelkező interfészeket, valószínűleg kihasználhatja ezt a funkciót és a nyelvi frissítés által engedélyezett általános matematikai osztályokat.

Ebben az oktatóanyagban a következőket sajátíthatja el:

  • Statikus tagokkal rendelkező felületek definiálása.
  • Interfészek használatával definiálhat olyan osztályokat, amelyek interfészeket implementálnak a definiált operátorokkal.
  • Statikus felületi metódusokra támaszkodó általános algoritmusok létrehozása.

Előfeltételek

Statikus absztrakciós felületi metódusok

Kezdjük egy példával. A következő metódus két double szám középpontját adja vissza:

public static double MidPoint(double left, double right) =>
    (left + right) / (2.0);

Ugyanez a logika bármilyen numerikus típusnál működik: int, short, long, , floatdecimal, vagy bármilyen számnak felel meg. Szüksége van egy módra az + és / operátorok használatához, és meg kell adnia egy értéket a következőhöz: 2. Az interfész használatával System.Numerics.INumber<TSelf> az előző metódust a következő általános módszerként írhatja:

public static T MidPoint<T>(T left, T right)
    where T : INumber<T> => (left + right) / T.CreateChecked(2);  // note: the addition of left and right may overflow here; it's just for demonstration purposes

Minden olyan típusnak, amely megvalósítja az INumber<TSelf> interfészt, tartalmaznia kell egy definíciót a következőhöz operator +és ehhez operator /: . A nevező úgy van definiálva T.CreateChecked(2) , hogy bármilyen numerikus típus értékét 2 létrehozza, ami arra kényszeríti a nevezőt, hogy ugyanaz legyen, mint a két paraméter. INumberBase<TSelf>.CreateChecked<TOther>(TOther) a megadott értékből létrehoz egy típuspéldányt, és egy olyan értéket ad OverflowException vissza, amely kívül esik a képviselhető tartományon. (Ez a megvalósítás túlcsordulást jelenthet, ha left és right mindkettő elég nagy érték. Vannak alternatív algoritmusok, amelyek elkerülhetik ezt a lehetséges problémát.)

A statikus absztrakt tagokat ismerős szintaxissal definiálhatja egy felületen: A static és abstract módosítókat hozzáadhatja bármely olyan statikus taghoz, amely nem biztosít implementációt. Az alábbi példa egy IGetNext<T> olyan felületet határoz meg, amely bármely felülbíráló operator ++típusra alkalmazható:

public interface IGetNext<T> where T : IGetNext<T>
{
    static abstract T operator ++(T other);
}

A típusargumentum által implementálható T korlátozás biztosítja, IGetNext<T>hogy az operátor aláírása tartalmazza a tartalmazott típust vagy annak típusargumentumát. Számos operátor kikényszeríti, hogy a paramétereknek meg kell egyezniük a típussal, vagy a típusparaméternek a tartalmazó típus implementálására kell kényszerítve lennie. E korlátozás nélkül az ++ operátor nem definiálható a IGetNext<T> felületen.

Létrehozhat egy struktúrát, amely egy "A" karakterből álló sztringet hoz létre, amelyben minden növekmény egy másik karaktert ad hozzá a sztringhez a következő kóddal:

public struct RepeatSequence : IGetNext<RepeatSequence>
{
    private const char Ch = 'A';
    public string Text = new string(Ch, 1);

    public RepeatSequence() {}

    public static RepeatSequence operator ++(RepeatSequence other)
        => other with { Text = other.Text + Ch };

    public override string ToString() => Text;
}

Általánosságban véve létrehozhat bármilyen algoritmust, amelyben a ++ kifejezést úgy szeretné meghatározni, hogy "ez a típusú érték következő előállítását" jelentse. Ennek a felületnek a használatával világos kódot és eredményeket érhet el.

var str = new RepeatSequence();

for (int i = 0; i < 10; i++)
    Console.WriteLine(str++);

Az előző példa a következő kimenetet hozza létre:

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

Ez a kis példa a funkció motivációját mutatja be. Természetes szintaxist használhat operátorokhoz, állandó értékekhez és egyéb statikus műveletekhez. Ezeket a technikákat akkor ismerheti meg, ha több statikus tagra támaszkodó típust hoz létre, beleértve a túlterhelt operátorokat is. Adja meg a típusok képességeinek megfelelő felületeket, majd deklarálja az új felület támogatását.

Általános matematika

A statikus metódusok, köztük az operátorok felületi engedélyezésének motiváló forgatókönyve az általános matematikai algoritmusok támogatása. A .NET 7 alaposztálykódtár számos aritmetikai operátor felületdefinícióit tartalmazza, valamint származtatott interfészeket, amelyek számos aritmetikai operátort egyesítenek egy INumber<T> felületen. Alkalmazzuk ezeket a típusokat egy Point<T> olyan rekord létrehozásához, amely bármilyen numerikus típust Thasználhat. A pontot XOffset és YOffset segítségével áthelyezheti a + operátorral.

Először hozzon létre egy új konzolalkalmazást a dotnet new vagy a Visual Studio használatával.

A nyilvános felületnek a Translation<T>Point<T> következő kódhoz hasonlóan kell kinéznie:

// Note: Not complete. This won't compile yet.
public record Translation<T>(T XOffset, T YOffset);

public record Point<T>(T X, T Y)
{
    public static Point<T> operator +(Point<T> left, Translation<T> right);
}

A típust mind a record típushoz, mind a Translation<T>Point<T> típushoz használhatja: Mindkettő két értéket tárol, és a kifinomult viselkedés helyett az adattárolást jelöli. A megvalósítás a operator + következő kódhoz hasonlóan fog kinézni:

public static Point<T> operator +(Point<T> left, Translation<T> right) =>
    left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };

Az előző kód fordításához deklarálnia kell, hogy T támogatja a IAdditionOperators<TSelf, TOther, TResult> felületet. Ez a felület tartalmazza a statikus metódust operator + . Három típusparamétert deklarál: egyet a bal operandushoz, egyet a jobb operandushoz, egyet az eredményhez. Egyes típusok különböző operandus- és eredménytípusokat implementálnak + . Adjon hozzá egy deklarációt, amely szerint a típusargumentum T implementálja a következőt IAdditionOperators<T, T, T>:

public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>

A korlátozás hozzáadása után az Point<T> osztály használhatja az + összeadási operátort. Adja hozzá ugyanezt a korlátozást a deklarációhoz Translation<T> :

public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;

A IAdditionOperators<T, T, T> korlátozás megakadályozza, hogy az osztályt használó fejlesztő olyan típust Translation hozzon létre, amely nem felel meg a pont hozzáadására vonatkozó korlátozásnak. Hozzáadta a szükséges korlátozásokat a típusparaméterhez Translation<T> , és Point<T> így ez a kód működik. A teszteléshez az alábbihoz hasonló kódot adhat hozzá a Translation fájl deklarációihoz Point:

var pt = new Point<int>(3, 4);

var translate = new Translation<int>(5, 10);

var final = pt + translate;

Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);

Ezt a kódot újrafelhasználhatóbbá teheti, ha deklarálja, hogy ezek a típusok a megfelelő aritmetikai interfészeket implementálják. Az első módosítás, hogy deklaráljuk, hogy a Point<T, T> implementálja a IAdditionOperators<Point<T>, Translation<T>, Point<T>> interfészt. A Point típus különböző típusú operandusokat és eredményeket használ. A Point típus már implementál egy operator + ilyen aláírást, így a felület hozzáadása a deklarációhoz mindössze annyit igényel:

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>

Végül, ha hozzáadást végez, hasznos lehet egy tulajdonság, amely meghatározza az adott típus additív identitásértékét. Ennek a funkciónak van egy új felülete: IAdditiveIdentity<TSelf,TResult>. Az additív identitás fordítása {0, 0} : Az eredményként kapott pont megegyezik a bal operandussal. Az IAdditiveIdentity<TSelf, TResult> interfész egy olvasható tulajdonságot határoz meg, AdditiveIdentityamely az identitás értékét adja vissza. A Translation<T> felület implementálásához néhány módosításra van szükség:

using System.Numerics;

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Translation<T> AdditiveIdentity =>
        new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
}

Itt van néhány változás, ezért járjuk végig őket egyenként. Először deklarálja, hogy a Translation típus implementálja a IAdditiveIdentity felületet:

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>

A következő lépésként megpróbálhatja implementálni a felület tagját az alábbi kódban látható módon:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: 0, YOffset: 0);

Az előző kód nem fordít le, mert 0 a típustól függ. A válasz: Használja IAdditiveIdentity<T>.AdditiveIdentity a 0. Ez a változás azt jelenti, hogy a megszorításoknak most tartalmazniuk kell, hogy az T megvalósítja az IAdditiveIdentity<T>-t. Ez a következő megvalósítást eredményezi:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);

Most, hogy hozzáadta ezt a kényszert a Translation<T>-hoz, ugyanazt a kényszert kell hozzáadnia a következőhöz: Point<T>.

using System.Numerics;

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Point<T> operator +(Point<T> left, Translation<T> right) =>
        left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
}

Ez a minta bemutatja, hogyan állnak össze a generikus matematika felületei. Megtanulta végrehajtani az alábbi műveleteket:

  • Írjon egy olyan metódust, amely az INumber<T> interfészre támaszkodik, hogy a metódus bármilyen numerikus típussal használható legyen.
  • Olyan típus létrehozása, amely az összeadási felületekre támaszkodik egy olyan típus implementálásához, amely csak egy matematikai műveletet támogat. Ez a típus deklarálja, hogy támogatja ezeket az interfészeket, így más módon is összeállítható. Az algoritmusok a matematikai operátorok legtermtermeltebb szintaxisával vannak megírva.

Kísérletezzen ezekkel a funkciókkal, és regisztráljon visszajelzést. Használhatja a Visszajelzés küldése menüpontot a Visual Studióban, vagy létrehozhat egy új problémát a GitHub roslyn-adattárában. Olyan általános algoritmusokat hozhat létre, amelyek bármilyen numerikus típussal működnek. Algoritmusokat hozhat létre ezen interfészek használatával, ahol a típusargumentum csak számszerű képességek egy részét valósítja meg. Még ha nem is készít új felületeket, amelyek ezeket a képességeket használják, kísérletezhet velük az algoritmusokban.

Lásd még