Kurz: Prozkoumání funkce C# 11 – statické virtuální členy v rozhraních

C# 11 a .NET 7 zahrnují statické virtuální členy v rozhraních. Tato funkce umožňuje definovat rozhraní, která zahrnují přetížené operátory nebo jiné statické členy. Jakmile definujete rozhraní se statickými členy, můžete tato rozhraní použít jako omezení k vytvoření obecných typů, které používají operátory nebo jiné statické metody. I když nevytvoříte rozhraní s přetíženými operátory, pravděpodobně tuto funkci získáte a obecné matematické třídy povolené aktualizací jazyka.

V tomto kurzu se naučíte:

  • Definujte rozhraní se statickými členy.
  • Pomocí rozhraní můžete definovat třídy, které implementují rozhraní s definovanými operátory.
  • Vytvořte obecné algoritmy, které spoléhají na metody statického rozhraní.

Požadavky

Budete muset nastavit počítač tak, aby běžel na platformě .NET 7, který podporuje C# 11. Kompilátor C# 11 je k dispozici od sady Visual Studio 2022 verze 17.3 nebo sady .NET 7 SDK.

Statické metody abstraktního rozhraní

Začněme příkladem. Následující metoda vrátí střední bod dvou double čísel:

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

Stejná logika by fungovala pro libovolný číselný typ: int, longshort, , floatdecimalnebo jakýkoli typ, který představuje číslo. Potřebujete mít způsob, jak používat operátory + a / definovat hodnotu pro 2. Pomocí rozhraní můžete System.Numerics.INumber<TSelf> napsat předchozí metodu jako následující obecnou metodu:

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

Jakýkoli typ, který implementuje INumber<TSelf> rozhraní, musí obsahovat definici pro operator +, a pro operator /. Jmenovatel je definován vytvořením T.CreateChecked(2) hodnoty 2 pro libovolný číselný typ, který vynutí jmenovatel být stejným typem jako dva parametry. INumberBase<TSelf>.CreateChecked<TOther>(TOther) vytvoří instanci typu ze zadané hodnoty a vyvolá OverflowException , pokud hodnota spadá mimo reprezentovatelný rozsah. (Tato implementace má potenciál přetečení, pokud left jsou obě right hodnoty dostatečně velké. Existují alternativní algoritmy, které se můžou tomuto potenciálnímu problému vyhnout.)

Statické abstraktní členy v rozhraní definujete pomocí známé syntaxe: Přidáte static modifikátory abstract do libovolného statického členu, který neposkytuje implementaci. Následující příklad definuje rozhraní, které lze použít u libovolného IGetNext<T> typu, který přepisuje operator ++:

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

Omezení, které argument typu T, implementuje IGetNext<T> , zajišťuje, že podpis operátoru obsahuje obsahující typ nebo jeho typ argumentu. Mnoho operátorů vynucuje, že jeho parametry musí odpovídat typu, nebo musí být parametr typu omezený pro implementaci obsahujícího typu. Bez tohoto omezení ++ nelze operátor v rozhraní definovat IGetNext<T> .

Můžete vytvořit strukturu, která vytvoří řetězec znaků "A", kde každý přírůstek přidá další znak do řetězce pomocí následujícího kódu:

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;
}

Obecněji můžete vytvořit libovolný algoritmus, ve kterém můžete chtít definovat ++ , aby se míněla "vytvořit další hodnotu tohoto typu". Pomocí tohoto rozhraní se vytvoří jasný kód a výsledky:

var str = new RepeatSequence();

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

Předchozí příklad vytvoří následující výstup:

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

Tento malý příklad ukazuje motivaci této funkce. Pro operátory, konstantní hodnoty a další statické operace můžete použít přirozenou syntaxi. Tyto techniky můžete prozkoumat při vytváření více typů, které spoléhají na statické členy, včetně přetížených operátorů. Definujte rozhraní, která odpovídají schopnostem vašich typů, a deklarujte podporu těchto typů pro nové rozhraní.

Obecná matematika

Motivační scénář povolení statických metod, včetně operátorů, v rozhraních je podpora obecných matematických algoritmů. Knihovna základních tříd .NET 7 obsahuje definice rozhraní pro mnoho aritmetických operátorů a odvozená rozhraní, která kombinují mnoho aritmetických operátorů v INumber<T> rozhraní. Pojďme tyto typy použít k vytvoření záznamu Point<T> , který může použít libovolný číselný typ pro T. Bod lze přesunout některými XOffset a YOffset pomocí operátoru + .

Začněte vytvořením nové konzolové aplikace, a to buď pomocí dotnet new sady Visual Studio, nebo pomocí sady Visual Studio.

Veřejné rozhraní a Translation<T>Point<T> mělo by vypadat jako následující kód:

// 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);
}

Tento typ použijete record pro oba Translation<T>Point<T> typy: Obě ukládají dvě hodnoty a představují úložiště dat místo sofistikovaného chování. Implementace operator + by vypadala jako následující kód:

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

Abyste mohli předchozí kód zkompilovat, musíte deklarovat, že T rozhraní podporuje IAdditionOperators<TSelf, TOther, TResult> . Toto rozhraní zahrnuje statickou metodu operator + . Deklaruje tři parametry typu: jeden pro levý operand, jeden pro pravý operand a jeden pro výsledek. Některé typy implementují + pro různé typy operandů a výsledků. Přidejte deklaraci, T kterou argument typu implementuje IAdditionOperators<T, T, T>:

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

Po přidání omezení může vaše Point<T> třída použít + operátor sčítání. Přidejte stejné omezení deklarace Translation<T> :

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

Omezení IAdditionOperators<T, T, T> brání vývojáři, který vaši třídu používá, aby vytvořil Translation pomocí typu, který nesplňuje omezení pro přidání do bodu. Do parametru Translation<T> typu jste přidali potřebná omezení, Point<T> a proto tento kód funguje. Můžete testovat tak, že přidáte kód podobný následujícímu kódu nad deklaracemi Translation souboru Program.cs a Point do souboru Program.cs :

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);

Tento kód můžete znovu použít deklarováním, že tyto typy implementují příslušná aritmetická rozhraní. První změnou, kterou provedete, je deklarovat, že Point<T, T> implementuje IAdditionOperators<Point<T>, Translation<T>, Point<T>> rozhraní. Typ Point používá pro operandy a výsledek různé typy. Typ Point již implementuje s tímto podpisem operator + , takže přidání rozhraní do deklarace je vše, co potřebujete:

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

A konečně, když provádíte sčítání, je užitečné mít vlastnost, která definuje hodnotu doplňkové identity pro daný typ. Pro tuto funkci je k dispozici nové rozhraní: IAdditiveIdentity<TSelf,TResult>. Překlad {0, 0} je přídatná identita: Výsledný bod je stejný jako levý operand. Rozhraní IAdditiveIdentity<TSelf, TResult> definuje jednu vlastnost jen pro čtení, AdditiveIdentitykterá vrací hodnotu identity. K Translation<T> implementaci tohoto rozhraní je potřeba provést několik změn:

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);
}

Tady je několik změn, takže si je projdeme jeden po druhém. Nejprve deklarujete, že Translation typ implementuje IAdditiveIdentity rozhraní:

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

Dále můžete zkusit implementovat člena rozhraní, jak je znázorněno v následujícím kódu:

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

Předchozí kód se nekompiluje, protože 0 závisí na typu. Odpověď: Použít IAdditiveIdentity<T>.AdditiveIdentity pro 0. Tato změna znamená, že vaše omezení musí nyní zahrnovat implementuje TIAdditiveIdentity<T>. Výsledkem je následující implementace:

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

Teď, když jste toto omezení Translation<T>přidali, musíte přidat stejné omezení do 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 };
}

Tato ukázka vám poskytla přehled o tom, jak rozhraní pro obecné matematické psaní. Naučili jste se:

  • Napište metodu INumber<T> , která spoléhala na rozhraní, aby se metoda dala použít s libovolným číselným typem.
  • Sestavte typ, který spoléhá na rozhraní sčítání k implementaci typu, který podporuje pouze jednu matematickou operaci. Tento typ deklaruje svou podporu pro stejná rozhraní, aby se mohl skládat jinými způsoby. Algoritmy se zapisují pomocí nejpřirozenější syntaxe matematických operátorů.

Experimentujte s těmito funkcemi a zaregistrujte zpětnou vazbu. V sadě Visual Studio můžete použít položku nabídky Odeslat názor nebo vytvořit nový problém v úložišti roslyn na GitHubu. Sestavte obecné algoritmy, které pracují s libovolným číselným typem. Sestavte algoritmy pomocí těchto rozhraní, kde argument typu může implementovat pouze podmnožinu funkcí podobných číslům. I když nevybudujete nová rozhraní, která tyto funkce používají, můžete experimentovat s jejich používáním ve svých algoritmech.

Viz také