Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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ě z této funkce budete mít prospěch a z obecných matematických tříd, které umožnila aktualizace 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
- Nejnovější sada .NET SDK
- editor Visual Studio Code editoru
- C# DevKit
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
, short
long
, , float
decimal
nebo 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 pomocí T.CreateChecked(2)
tak, aby vytvořil hodnotu 2
pro libovolný číselný typ, což vynucuje, aby jmenovatel měl stejný typ jako oba parametry.
INumberBase<TSelf>.CreateChecked<TOther>(TOther) vytvoří instanci typu ze zadané hodnoty a vyvolá OverflowException , pokud hodnota spadá mimo reprezentovatelný rozsah. (Tato implementace může způsobit přetečení, pokud jsou left
a right
obě dostatečně velké hodnoty. Existují alternativní algoritmy, které se tomuto potenciálnímu problému mohou 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 typu IGetNext<T>
, který překrývá 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 jejich parametry musí odpovídat danému typu, nebo že typový parametr musí být omezen tak, aby implementoval obsahující typ. Bez tohoto omezení by operátor ++
nebylo možné definovat v rozhraní 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, buď pomocí dotnet new
, nebo pomocí sady Visual Studio.
Veřejné rozhraní pro Translation<T>
a Point<T>
by mělo 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, že argument typu T
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>
zabraňuje vývojáři, který používá vaši třídu, ve vytvoření Translation
pomocí typu, který nesplňuje požadavky na přidání k bodu. Přidali jste potřebná omezení k parametrům typu Translation<T>
a Point<T>
, takže 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.
Point
typ již implementuje operator +
s daným podpisem, takže stačí přidat rozhraní do deklarace.
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 aditivní identity pro daný typ. Pro tuto funkci je k dispozici nové rozhraní: IAdditiveIdentity<TSelf,TResult>. Překlad {0, 0}
je aditivní identita: Výsledný bod je stejný jako levý operand. Rozhraní IAdditiveIdentity<TSelf, TResult>
definuje jednu vlastnost jen pro čtení, AdditiveIdentity
která 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, že T
implementuje IAdditiveIdentity<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 pohled na to, jak se skládají rozhraní pro obecnou matematiku. 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.