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


Oktatóanyag: A C# 11 funkció felfedezése – statikus virtuális tagok a felületeken

A C# 11 és a .NET 7 statikus virtuális tagokat tartalmaz a felületeken. Ez a funkció lehetővé teszi túlterhelt operátorokat vagy más statikus tagokat tartalmazó interfészek definiálására. 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.

Az oktatóanyag segítségével megtanulhatja a következőket:

  • 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

Be kell állítania a gépet a .NET 7 futtatására, amely támogatja a C# 11-et. A C# 11 fordító a Visual Studio 2022 17.3-tól vagy a .NET 7 SDK-tól kezdve érhető el.

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, , float decimal, vagy bármilyen számnak felel meg. Rendelkeznie kell az operátorok és / az + operátorok használatának módjával, és meg kell adnia egy értéket a következőhöz2: . 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 módosításokat és abstract módosítókat static bármely olyan statikus taghoz hozzáadhatja, 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ó IGetNext<T> korlátozás biztosítja, Thogy 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 egyeznie a típussal, vagy a típusparaméternek kell lenniük a tartalmazó típus implementálásához. 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ánosabban bármely olyan algoritmust létrehozhat, amelyben definiálni ++ szeretné a "következő ilyen típusú érték előállítását". Ennek a felületnek a használatával egyértelmű kódot és eredményeket hoz létre:

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 egyesével XOffset áthelyezheti, és YOffset használhatja az operátort + .

Először hozzon létre egy új konzolalkalmazást, akár a használatával, akár a Visual Studióval dotnet new .

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 a kód működik. A teszteléshez az alábbihoz hasonló kódot adhat hozzá a Program.cs fájl deklarációihoz Translation 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 az interfész implementálásának IAdditionOperators<Point<T>, Translation<T>, Point<T>> deklarálásaPoint<T, T>. 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 lesz lefordítva, 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 korlátozásoknak tartalmazniuk kell az T implementálásokat IAdditiveIdentity<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 korlátozást Translation<T>, 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 jelennek meg az általános matematikai összeállítás 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észhalmazát implementálhatja. 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