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.
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách schůze o návrhu jazyka (LDM).
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/4436
Shrnutí
Rozhraní může určit abstraktní statické členy, pro které musí implementující třídy a struktury poskytnout explicitní nebo implicitní implementaci. K členům lze přistupovat prostřednictvím parametrů typu, které jsou omezeny rozhraním.
Motivace
V současné době neexistuje způsob, jak abstraktovat statické členy a psát generalizovaný kód, který se vztahuje na typy definující tyto statické členy. To je obzvláště problematické u druhů komponent, které existují pouze v statické formě, zejména u operátorů.
Tato funkce umožňuje obecné algoritmy nad číselnými typy reprezentované omezeními rozhraní, které určují přítomnost daných operátorů. Algoritmy lze proto vyjádřit z hlediska těchto operátorů:
// Interface specifies static properties and operators
interface IAddable<T> where T : IAddable<T>
{
static abstract T Zero { get; }
static abstract T operator +(T t1, T t2);
}
// Classes and structs (including built-ins) can implement interface
struct Int32 : …, IAddable<Int32>
{
static Int32 IAddable.operator +(Int32 x, Int32 y) => x + y; // Explicit
public static int Zero => 0; // Implicit
}
// Generic algorithms can use static members on T
public static T AddAll<T>(T[] ts) where T : IAddable<T>
{
T result = T.Zero; // Call static operator
foreach (T t in ts) { result += t; } // Use `+`
return result;
}
// Generic method can be applied to built-in and user-defined types
int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 });
Syntax
Členy rozhraní
Tato funkce by umožňovala deklarovat virtuální členy statického rozhraní.
Pravidla před C# 11
Před C# 11 jsou členy instance v rozhraních implicitně abstraktní (nebo virtuální, pokud mají výchozí implementaci), ale mohou volitelně mít modifikátor abstract (nebo virtual). Členy jiných než virtuálních instancí musí být explicitně označeny jako sealed.
Členové statického rozhraní jsou dnes implicitně ne-virtuální a neumožňují abstract, virtual nebo modifikátory sealed.
Návrh
Abstraktní statické členy
Členové statického rozhraní kromě polí mohou mít také modifikátor abstract. Abstraktní statické členy nesmí mít implementaci (a v případě vlastností nejsou povoleny přístupové metody s implementací).
interface I<T> where T : I<T>
{
static abstract void M();
static abstract T P { get; set; }
static abstract event Action E;
static abstract T operator +(T l, T r);
static abstract bool operator ==(T l, T r);
static abstract bool operator !=(T l, T r);
static abstract implicit operator T(string s);
static abstract explicit operator string(T t);
}
Virtuální statické členy
Členové statického rozhraní kromě polí mohou mít také modifikátor virtual. Virtuální statické členy musí mít tělo.
interface I<T> where T : I<T>
{
static virtual void M() {}
static virtual T P { get; set; }
static virtual event Action E;
static virtual T operator +(T l, T r) { throw new NotImplementedException(); }
}
Explicitně nevirtuální statické členy
Pro symetrii s nevirtuálními členy instance by statickým členům (s výjimkou polí) měl být povolen volitelný modifikátor sealed, i když jsou ve výchozím nastavení nevirtuální.
interface I0
{
static sealed void M() => Console.WriteLine("Default behavior");
static sealed int f = 0;
static sealed int P1 { get; set; }
static sealed int P2 { get => f; set => f = value; }
static sealed event Action E1;
static sealed event Action E2 { add => E1 += value; remove => E1 -= value; }
static sealed I0 operator +(I0 l, I0 r) => l;
}
Implementace členů rozhraní
Dnešní pravidla
Třídy a struktury mohou implementovat abstraktní členy instancí rozhraní implicitně nebo explicitně. Implicitně implementovaný člen rozhraní je běžný člen (virtuální nebo nevirtuální) deklarace třídy nebo struktury, která rovněž "plní roli" implementace člena rozhraní. Člen může být dokonce zděděný ze základní třídy, a proto ani není k dispozici v deklaraci třídy.
Explicitně implementovaný člen rozhraní používá kvalifikovaný název k identifikaci příslušného člena rozhraní. Implementace není přímo přístupná jako člen třídy nebo struktury, ale pouze prostřednictvím rozhraní.
Návrh
Ve třídách a strukturách není nutná žádná nová syntaxe, která by usnadnila implicitní implementaci statických abstraktních členů rozhraní. Stávající deklarace statického členu slouží k danému účelu.
Explicitní implementace statických abstraktních členů rozhraní používají kvalifikovaný název spolu s modifikátorem static.
class C : I<C>
{
string _s;
public C(string s) => _s = s;
static void I<C>.M() => Console.WriteLine("Implementation");
static C I<C>.P { get; set; }
static event Action I<C>.E // event declaration must use field accessor syntax
{
add { ... }
remove { ... }
}
static C I<C>.operator +(C l, C r) => new C($"{l._s} {r._s}");
static bool I<C>.operator ==(C l, C r) => l._s == r._s;
static bool I<C>.operator !=(C l, C r) => l._s != r._s;
static implicit I<C>.operator C(string s) => new C(s);
static explicit I<C>.operator string(C c) => c._s;
}
Sémantika
Omezení operátorů
V současné době mají všechny deklarace unárního a binárního operátoru určitý požadavek zahrnující alespoň jeden z jejich operandů být typu T nebo T?, kde T je typ instance ohraničujícího typu.
Tyto požadavky musí být uvolněny, aby omezený operand mohl být parametrem typu, který je považován za "typ instance obklopujícího typu".
Aby se parametr typu T počítal jako "typ instance obalujícího typu", musí splňovat následující požadavky:
-
Tje parametr přímého typu v rozhraní, ve kterém se deklarace operátoru vyskytuje, a -
Tje přímo omezen tím, co specifikace nazývá "typ instance" – tj. okolním rozhraním s vlastními parametry typu, které se používají jako argumenty typu.
Operátory rovnosti a převody
V rozhraních budou povoleny abstraktní/virtuální deklarace operátorů == a != a také abstraktní/virtuální deklarace implicitních a explicitních převodních operátorů. Odvozená rozhraní budou také povolena k jejich implementaci.
Pro operátory == a != musí být alespoň jeden parametr typu, který je považován za "typ instance ohraničujícího typu", jak bylo definováno v předchozí části.
Implementace statických abstraktních členů
Pravidla pro případy, kdy je deklarace statického členu ve třídě nebo struktuře považována za implementaci statického abstraktního členu rozhraní a pro jaké požadavky platí, pokud ano, jsou stejné jako pro členy instance.
TBD: Zde mohou být další nebo jiná pravidla nezbytná, o které jsme si zatím nepřemýšleli.
Rozhraní jako argumenty typu
Probrali jsme problém vyvolaný https://github.com/dotnet/csharplang/issues/5955 a rozhodli jsme se přidat omezení týkající se použití rozhraní jako argumentu typu (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts). Toto je omezení, které navrhl https://github.com/dotnet/csharplang/issues/5955 a schválilo LDM.
Rozhraní obsahující nebo dědící statického abstraktního či virtuálního člena, který nemá v tomto rozhraní nejvíce konkrétní implementaci, nelze použít jako argument typu. Pokud mají všechny statické abstraktní nebo virtuální členy nejvýraznější implementaci, lze rozhraní použít jako argument typu.
Přístup ke statickým abstraktním členům rozhraní
Ke statickému abstraktnímu členu rozhraní M lze přistupovat u parametru typu T pomocí výrazu T.M, pokud je T omezen rozhraním I a M je přístupný statický abstraktní člen I.
T M<T>() where T : I<T>
{
T.M();
T t = T.P;
T.E += () => { };
return t + T.P;
}
Za běhu se použije implementace konkrétního člena, která existuje u konkrétního typu, který je zadán jako typový argument.
C c = M<C>(); // The static members of C get called
Vzhledem k tomu, že výrazy dotazu jsou specifikované jako syntaktické přepsání, jazyk C# ve skutečnosti umožňuje použít typ jako zdroj dotazu, pokud má statické členy pro operátory dotazu, které používáte. Jinými slovy, pokud se syntax hodí, povolíme to! Myslíme si, že toto chování nebylo záměrné ani důležité v původním LINQ a nechceme, aby se podporovalo u parametrů typu. Pokud existují scénáře, o kterých uslyšíme, můžeme se rozhodnout je později přijmout.
Bezpečnost rozptylu §18.2.3.2
Pravidla zabezpečení odchylek by se měla vztahovat na podpisy statických abstraktních členů. Návrh na doplnění v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/variance-safety-for-static-interface-members.md#variance-safety by měl být upraven z
Tato omezení se nevztahují na výskyty typů v rámci deklarací statických členů.
k
Tato omezení se nevztahují na výskyty typů v rámci deklarací nevirtuálních ani neabstraktních statických členů.
§10.5.4 uživatelem definované implicitní převody
Následující odrážky
- Určete typy
S,S₀aT₀.- Pokud má
Etyp, ať jeStímto typem. - Pokud jsou
SneboTnulovatelné hodnotové typy, pak jsouSᵢaTᵢjejich podkladovými typy, jinak jsouSᵢaTᵢodpovídajícímiSaT. - Pokud jsou parametry typu
SᵢneboTᵢ, nechteS₀aT₀být jejich efektivními základními třídami, jinak nechteS₀aT₀býtSₓaTᵢ.
- Pokud má
- Najděte sadu typů,
D, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zS0(pokudS0je třída nebo struktura), základní třídyS0(pokudS0je třída) aT0(pokudT0je třída nebo struktura). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U. Tato sada se skládá z uživatelem definovaných a implicitně zvednutých konverzních operátorů deklarovaných třídami nebo strukturami vD, které převádějí z typu, který pokrýváS, na typ, který je pokrývanýT. Pokud jeUprázdný, převod není definován a dojde k chybě v době kompilace.
jsou upraveny takto:
- Určete typy
S,S₀aT₀.- Pokud má
Etyp, ať jeStímto typem. - Pokud jsou
SneboTnulovatelné hodnotové typy, pak jsouSᵢaTᵢjejich podkladovými typy, jinak jsouSᵢaTᵢodpovídajícímiSaT. - Pokud jsou parametry typu
SᵢneboTᵢ, nechteS₀aT₀být jejich efektivními základními třídami, jinak nechteS₀aT₀býtSₓaTᵢ.
- Pokud má
- Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U.- Najděte sadu typů,
D1, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zS0(pokudS0je třída nebo struktura), základní třídyS0(pokudS0je třída) aT0(pokudT0je třída nebo struktura). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U1. Tato sada se skládá z uživatelem definovaných a implicitně zvednutých konverzních operátorů deklarovaných třídami nebo strukturami vD1, které převádějí z typu, který pokrýváS, na typ, který je pokrývanýT. - Pokud
U1není prázdný,UjeU1. Jinak- Najděte sadu typů,
D2, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zSᵢefektivní sady rozhraní a jejich základních rozhraní (pokudSᵢje parametr typu) aTᵢefektivní sady rozhraní (pokudTᵢje parametr typu). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U2. Tato sada se skládá z uživatelem definovaných a zvednutých implicitních konverzních operátorů deklarovaných rozhraními vD2, které převádějí z typu, který zahrnujeS, na typ, který zahrnujeT. - Pokud
U2není prázdný,UjeU2
- Najděte sadu typů,
- Najděte sadu typů,
- Pokud je
Uprázdný, převod není definován a dojde k chybě v době kompilace.
§10.3.9 explicitní konverze definované uživatelem
Následující odrážky
- Určete typy
S,S₀aT₀.- Pokud má
Etyp, ať jeStímto typem. - Pokud jsou
SneboTnulovatelné hodnotové typy, pak jsouSᵢaTᵢjejich podkladovými typy, jinak jsouSᵢaTᵢodpovídajícímiSaT. - Pokud jsou parametry typu
SᵢneboTᵢ, nechteS₀aT₀být jejich efektivními základními třídami, jinak nechteS₀aT₀býtSᵢaTᵢ.
- Pokud má
- Najděte sadu typů,
D, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zS0(pokudS0je třída nebo struktura), základní třídyS0(pokudS0je třída),T0(pokudT0je třída nebo struktura) a základní třídyT0(pokudT0je třída). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U. Tato sada se skládá z uživatelem definovaných implicitních nebo explicitních konverzních operátorů deklarovaných třídami nebo strukturami veD, které převádějí z typu zahrnujícího nebo zahrnutého vSna typ zahrnující nebo zahrnutý vT. Pokud jeUprázdný, převod není definován a dojde k chybě v době kompilace.
jsou upraveny takto:
- Určete typy
S,S₀aT₀.- Pokud má
Etyp, ať jeStímto typem. - Pokud jsou
SneboTnulovatelné hodnotové typy, pak jsouSᵢaTᵢjejich podkladovými typy, jinak jsouSᵢaTᵢodpovídajícímiSaT. - Pokud jsou parametry typu
SᵢneboTᵢ, nechteS₀aT₀být jejich efektivními základními třídami, jinak nechteS₀aT₀býtSᵢaTᵢ.
- Pokud má
- Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U.- Najděte sadu typů,
D1, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zS0(pokudS0je třída nebo struktura), základní třídyS0(pokudS0je třída),T0(pokudT0je třída nebo struktura) a základní třídyT0(pokudT0je třída). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U1. Tato sada se skládá z uživatelem definovaných implicitních nebo explicitních konverzních operátorů deklarovaných třídami nebo strukturami veD1, které převádějí z typu zahrnujícího nebo zahrnutého vSna typ zahrnující nebo zahrnutý vT. - Pokud
U1není prázdný,UjeU1. Jinak- Najděte sadu typů,
D2, ze kterých se budou považovat uživatelem definované operátory převodu. Tato sada se skládá zSᵢefektivní sady rozhraní a jejich základních rozhraní (pokudSᵢje parametr typu) aTᵢefektivní sady rozhraní a jejich základní rozhraní (pokudTᵢje parametr typu). - Vyhledejte množinu použitelných uživatelsky definovaných a povýšených operátorů převodu
U2. Tato sada se skládá z uživatelem definovaných a povolených implicitních nebo explicitních konverzních operátorů deklarovaných rozhraními vD2, které převádějí z typu obsahujícího nebo obsaženého vSna typ obsahující nebo obsažený vT. - Pokud
U2není prázdný,UjeU2
- Najděte sadu typů,
- Najděte sadu typů,
- Pokud je
Uprázdný, převod není definován a dojde k chybě v době kompilace.
Výchozí implementace
další funkcí tohoto návrhu je umožnit statickým virtuálním členům v rozhraních mít výchozí implementace, stejně jako to dělají virtuální/abstraktní členové instance.
Jednou z komplikací je, že výchozí implementace by chtěly volat ostatní statické virtuální členy virtuálně. Povolení zavolání statických virtuálních členů přímo v rozhraní by vyžadovalo tok skrytého parametru typu představujícího typ "self", na kterém se skutečně vyvolala aktuální statická metoda. Zdá se to složité, nákladné a potenciálně matoucí.
Probrali jsme jednodušší verzi, která zachovává omezení aktuálního návrhu, že statické virtuální členy mohou pouze být vyvolány u parametrů typu. Vzhledem k tomu, že rozhraní se statickými virtuálními členy často mají explicitní parametr typu představující typ "self", nejedná se o velkou ztrátu: ostatní statické virtuální členy lze volat na tomto typu "self". Tato verze je mnohem jednodušší a zdá se docela proveditelná.
V https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics jsme se rozhodli podporovat výchozí implementace statických členů, které následují/rozšiřují pravidla stanovená v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md odpovídajícím způsobem.
Porovnávání vzorů
Při daném kódu může uživatel rozumně očekávat, že vypíše "True" (stejně jako kdyby byl konstantní vzor psán inline):
M(1.0);
static void M<T>(T t) where T : INumberBase<T>
{
Console.WriteLine(t is 1); // Error. Cannot use a numeric constant
Console.WriteLine((t is int i) && (i is 1));
}
Vzhledem k tomu, že vstupní typ vzoru není double, konstantní 1 vzor nejprve zkontroluje příchozí T proti int. To je neintuitivní, takže se zablokuje, dokud budoucí verze jazyka C# nepřidá lepší zpracování pro číselné porovnávání s typy odvozenými z INumberBase<T>. K tomu řekneme, že explicitně rozpoznáme INumberBase<T> jako typ, ze kterého budou odvozena všechna čísla, a zablokujeme vzor, pokud se snažíme shodovat číselný konstantní vzor s číselným typem, ve kterém nemůžeme znázorňovat vzor (tj. parametr typu omezený na INumberBase<T>nebo uživatelem definovaný typ čísla, který dědí z INumberBase<T>).
Formálně přidáme výjimku k definici vzorové kompatibility pro konstantní vzory:
Konstantní vzor testuje hodnotu výrazu proti konstantní hodnotě. Konstantou může být libovolný konstantní výraz, například literál, název deklarované proměnné
constnebo výčtová konstanta. Pokud vstupní hodnota není otevřeným typem, konstantní výraz se implicitně převede na typ odpovídajícího výrazu; Pokud typ vstupní hodnoty není vzorově kompatibilní s typem konstantního výrazu, je operace porovnávání vzorů chybou. Pokud je konstantní výraz, který má být porovnán, číselnou hodnotou, a vstupní hodnota je typ, který dědí zSystem.Numerics.INumberBase<T>, a neexistuje žádný konstantní převod z konstantního výrazu na typ vstupní hodnoty, pak je operace porovnávání vzorů chyba.
Přidáme také podobnou výjimku pro relační vzory:
Pokud je vstup typem, pro který je definován vhodný integrovaný binární relační operátor, je použitý se vstupem jako levý operand a s danou konstantou jako pravý operand, je vyhodnocení tohoto operátoru považováno za význam relačního vzoru. V opačném případě převedeme vstup na typ výrazu pomocí explicitního převodu s možnou hodnotou null nebo rozbalení. Jedná se o chybu v době kompilace, pokud neexistuje žádný takový převod. Jedná se o chybu v době kompilace, pokud je vstupním typem parametr omezený na typ nebo typ dědící z
System.Numerics.INumberBase<T>a vstupní typ nemá definovaný žádný vhodný integrovaný binární relační operátor. Vzor se považuje za neodpovídající, pokud převod selže. Pokud převod proběhne úspěšně, výsledkem operace porovnávání vzorů je výsledek vyhodnocení výrazu e OP v, kde e je převedený vstup, OP je relační operátor a v je konstantní výraz.
Nevýhody
- "statická abstrakce" je nový koncept, který bude smysluplně přidávat do koncepčního zatížení jazyka C#.
- Není to levná funkce k sestavení. Měli bychom se ujistit, že to stojí za to.
Alternativy
Strukturální omezení
Alternativním přístupem by bylo mít přímo "strukturální omezení" a explicitně vyžadovat přítomnost konkrétních operátorů u parametru typu. Nevýhody toho jsou: - To by se muselo napsat pokaždé. Pojmenované omezení se zdá být lepší. - Jedná se o zcela nový druh omezení, zatímco navrhovaná funkce využívá stávající koncept omezení rozhraní. - Fungovalo by to jenom pro operátory, ne (snadno) pro jiné druhy statických členů.
Nevyřešené otázky
Statická abstraktní rozhraní a statické třídy
Další informace najdete v tématu https://github.com/dotnet/csharplang/issues/5783 a https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes.
Schůzky o navrhování
- https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-08.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-06.md
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md
C# feature specifications