Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Jegyzet
Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.
A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.
A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.
Bajnoki probléma: https://github.com/dotnet/csharplang/issues/4436
Összefoglalás
Az interfész megengedheti, hogy absztrakt statikus tagokat határozzon meg, amelyekhez az implementáló osztályoknak és struktúráknak explicit vagy implicit implementációt kell biztosítaniuk. A tagok az interfész által korlátozott típusparaméterek közül érhetők el.
Motiváció
Jelenleg nincs mód arra, hogy absztrakciót alkalmazzon a statikus tagok felett, és általánosított kódot írjon, amely a statikus tagokat meghatározó típusokra vonatkozik. Ez különösen problémás az olyan tagtípusok esetében, amelyek csak léteznek statikus formában, különösen az operátorok esetében.
Ez a funkció általános algoritmusokat tesz lehetővé numerikus típusok felett, amelyeket az adott operátorok jelenlétét meghatározó felületi megkötések jelölnek. Az algoritmusok tehát az ilyen operátorok tekintetében kifejezhetők:
// 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 });
Szintaxis
Interfésztagok
A funkció lehetővé tenné a statikus interfész tagok virtuálisként történő deklarálását.
Szabályok a C# 11 előtt
A C# 11 előtt a felületi példányok tagjai implicit módon absztraktak (vagy virtuálisak, ha alapértelmezett implementációval rendelkeznek), de szükség esetén abstract (vagy virtual) módosítókkal is rendelkezhetnek. A nem virtuális példánytagokat explicit módon kell sealedmegjelölni.
A statikus felület tagjai ma implicit módon nem virtuálisak, és nem engedélyezik abstract, virtual vagy sealed módosítókat.
Javaslat
Absztrakt statikus tagok
A mezőkön kívüli statikus illesztőtagok is használhatják a abstract módosító elemet. Az absztrakt statikus tagok nem rendelkezhetnek törzsekkel (vagy tulajdonságok esetén a tartozékokkal nem rendelkezhet törzs).
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ális statikus tagok
A mezőkön kívüli statikus illesztőtagok is használhatják a virtual módosító elemet. A virtuális statikus tagoknak rendelkezniük kell egy törzsvel.
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(); }
}
Kifejezetten nem virtuális statikus tagok
A nem virtuális példányok tagjaival való szimmetria esetén a statikus tagoknak (kivéve a mezőket) engedélyezni kell egy választható sealed módosító használatát, annak ellenére, hogy alapértelmezés szerint nem virtuálisak:
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;
}
Az interfésztagok implementálása
A mai szabályok
Az osztályok és a szerkezetek implicit vagy explicit módon implementálhatják az interfészek absztrakt példányainak tagjait. Az implicit módon implementált interfésztag az osztály vagy a struktúra egy normál (virtuális vagy nem virtuális) tagdeklarációja, amely egyszerűen csak "véletlenül" az interfész tagját is megvalósítja. A tag akár egy alaposztályból is örökölhető, így még az osztálydeklarációban sem szerepelhet.
Egy explicit módon implementált felülettag minősített névvel azonosítja a szóban forgó felülettagot. A megvalósítás közvetlenül nem érhető el tagként az osztályon vagy a szerkezeten, hanem csak a felületen keresztül.
Javaslat
Az osztályokban és a szerkezetekben nincs szükség új szintaxisra a statikus absztrakt interfésztagok implicit megvalósításának megkönnyítéséhez. A meglévő statikus tag deklarációk ezt a célt szolgálják.
A statikus absztrakt felülettagok explicit implementációi a static módosítóval együtt minősített nevet használnak.
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;
}
Szemantika
Operátorkorlátozások
Ma minden nem naplós és bináris operátor-deklarációnak van valamilyen követelménye, amely szerint az operandusok legalább egyikének T vagy T?típusúnak kell lennie, ahol a T a beágyazási típus példánytípusa.
Ezeket a követelményeket enyhíteni kell, hogy a korlátozott operandus olyan típusparaméter lehessen, amely a "befoglaló típus példánytípusának" minősül.
Ahhoz, hogy egy típusparaméter T "a beágyazási típus példánytípusának" számíthasson, meg kell felelnie a következő követelményeknek:
-
Tegy közvetlen típusparaméter azon a felületen, amelyen az operátor deklarációja történik, és -
Tközvetlenül korlátozza, amit a specifikáció "példánytípusnak" nevez – azaz a környező felületnek a típusargumentumként használt saját típusparamétereivel.
Egyenlőségi operátorok és konverziók
A == és != operátorok absztrakt/virtuális deklarációi, valamint az implicit és explicit konverziós operátorok absztrakt/virtuális deklarációi engedélyezve lesznek a felületeken. A származtatott interfészeknek is megengedik, hogy implementálhassák őket.
Az == és != operátorok esetében legalább egy paramétertípusnak olyan típusparaméternek kell lennie, amely az előző szakaszban meghatározott "a beágyazási típus példánytípusának" számít.
Statikus absztrakt tagok implementálása
Az osztályban vagy struktúrában lévő statikus tagdeklaráció akkor minősül statikus absztrakt interfész tag implementálásának, és akkor vonatkoznak rá a követelmények, ha megegyeznek a példánytagokkal.
TBD: Előfordulhat, hogy további vagy eltérő szabályokra van szükség, amelyekre még nem gondoltunk.
Illesztők típusargumentumként
Megvitattuk a https://github.com/dotnet/csharplang/issues/5955 által felvetett problémát, és úgy döntöttünk, hogy típusargumentumként (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts) korlátozzuk a felület használatát. Az alábbiakban a https://github.com/dotnet/csharplang/issues/5955 által javasolt és az LDM által jóváhagyott korlátozást találjuk.
Nem használható típusargumentumként olyan statikus absztrakt/virtuális tagot tartalmazó vagy öröklő felület, amely nem rendelkezik a legspecifikusabb implementációval az illesztőben. Ha az összes statikus absztrakt/virtuális tag rendelkezik a legspecifikusabb implementációval, a felület típusargumentumként használható.
Statikus absztrakt felülettagok elérése
A statikus absztrakt felület tagja M a T típusparaméteren érhető el a T.M kifejezés használatával, amikor T egy I interfésszel van korlátozva, és M egy hozzáférhető statikus absztrakt tagja I.
T M<T>() where T : I<T>
{
T.M();
T t = T.P;
T.E += () => { };
return t + T.P;
}
Futásidőben az a tényleges tag-implementáció kerül felhasználásra, amely a tényleges, típusargumentumként megadott típuson létezik.
C c = M<C>(); // The static members of C get called
Mivel a lekérdezési kifejezések szintaktikai újraírásként vannak definiálva, a C# lehetővé teszi, hogy típusú-et használjon lekérdezési forrásként, amennyiben vannak statikus tagjai az alkalmazott lekérdezési operátorokhoz! Más szóval, ha a szintaxis illik, akkor megengedjük! Úgy gondoljuk, hogy ez a viselkedés nem volt szándékos vagy fontos az eredeti LINQ-ban, és nem szeretnénk támogatást biztosítani a típusparamétereknél. Ha vannak olyan forgatókönyvek, amelyekről hallani fogunk, és később dönthetünk úgy, hogy ezt elfogadjuk.
Variancia biztonsági §18.2.3.2
A varianciára vonatkozó biztonsági szabályokat a statikus absztrakt tagok aláírására kell alkalmazni. A https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/variance-safety-for-static-interface-members.md#variance-safety javasolt kiegészítést módosítani kell.
Ezek a korlátozások nem vonatkoznak a statikus tagok deklarációiban lévő típusok előfordulására.
hoz
Ezek a korlátozások nem vonatkoznak a nem virtuális, nem absztrakt statikus tagok deklarációiban szereplő típusok előfordulására.
§10.5.4 Felhasználó által definiált implicit konverziók
Az alábbi felsoroláspontok
- Határozza meg a
S,S₀ésT₀típusokat.- Ha
Erendelkezik típussal,Slegyen ilyen típus. - Ha
SvagyTnull értékűek, hagyja, hogySᵢésTᵢlegyen a mögöttes típusuk, különbenSᵢésTᵢlegyenSésT. - Ha
SᵢvagyTᵢtípusparaméterek, hagyja, hogyS₀ésT₀legyen a tényleges alaposztályuk, különbenS₀ésT₀legyenSₓésTᵢ.
- Ha
- Keresse meg a
Dtípuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletS0(haS0osztály vagy szerkezet), aS0alaposztályai (haS0osztály) ésT0(haT0osztály vagy szerkezet). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U. Ez a készlet aDosztályai vagy struktúrái által deklarált, felhasználó által definiált és emelt implicit konverziós operátorokból áll, amelyek egyS-et magában foglaló típusból egyTáltal átfoglalt típussá alakítanak. HaUüres, a konvertálás nincs meghatározva, és fordítási időhiba lép fel.
az alábbiak szerint vannak módosítva:
- Határozza meg a
S,S₀ésT₀típusokat.- Ha
Erendelkezik típussal,Slegyen ilyen típus. - Ha
SvagyTnull értékűek, hagyja, hogySᵢésTᵢlegyen a mögöttes típusuk, különbenSᵢésTᵢlegyenSésT. - Ha
SᵢvagyTᵢtípusparaméterek, hagyja, hogyS₀ésT₀legyen a tényleges alaposztályuk, különbenS₀ésT₀legyenSₓésTᵢ.
- Ha
- Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U.- Keresse meg a
D1típuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletS0(haS0osztály vagy szerkezet), aS0alaposztályai (haS0osztály) ésT0(haT0osztály vagy szerkezet). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U1. Ez a készlet aD1osztályai vagy struktúrái által deklarált, felhasználó által definiált és emelt implicit konverziós operátorokból áll, amelyek egyS-et magában foglaló típusból egyTáltal átfoglalt típussá alakítanak. - Ha
U1nem üres, akkorUU1. Egyébként- Keresse meg a
D2típuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletSᵢhatékony illesztőkészletből és azok alapillesztőiből áll (haSᵢtípusparaméter), valamintTᵢhatékony illesztőkészletből (haTᵢtípusparaméter). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U2. A készlet aD2interfészei által deklarált, felhasználó által definiált és emelt implicit konverziós operátorokat foglalja magában, amelyek olyan típusból konvertálnak, ami magában foglaljaS-et, egy olyan típusra, amelyetTfoglal magában. - Ha
U2nem üres, akkorUU2van.
- Keresse meg a
- Keresse meg a
- Ha
Uüres, a konvertálás nincs meghatározva, és fordítási időhiba lép fel.
§10.3.9 felhasználó által meghatározott explicit konverziók
Az alábbi felsoroláspontok
- Határozza meg a
S,S₀ésT₀típusokat.- Ha
Erendelkezik típussal,Slegyen ilyen típus. - Ha
SvagyTnull értékűek, hagyja, hogySᵢésTᵢlegyen a mögöttes típusuk, különbenSᵢésTᵢlegyenSésT. - Ha
SᵢvagyTᵢtípusparaméterek, hagyja, hogyS₀ésT₀legyen a tényleges alaposztályuk, különbenS₀ésT₀legyenSᵢésTᵢ.
- Ha
- Keresse meg a
Dtípuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletS0(haS0osztály vagy struktúra), aS0alaposztályai (haS0osztály),T0(haT0osztály vagy struktúra), valamint aT0alaposztályai (haT0osztály). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U. Ez a készlet az osztályok vagy struktúrák által aD-ben deklarált, felhasználó által definiált és emelt implicit vagy explicit konverziós operátorokból áll, amelyek egySáltal magában foglalt vagy körülhatárolt típusból egyTáltal magában foglalt vagy körülhatárolt típussá alakítanak át. HaUüres, a konvertálás nincs meghatározva, és fordítási időhiba lép fel.
az alábbiak szerint vannak módosítva:
- Határozza meg a
S,S₀ésT₀típusokat.- Ha
Erendelkezik típussal,Slegyen ilyen típus. - Ha
SvagyTnull értékűek, hagyja, hogySᵢésTᵢlegyen a mögöttes típusuk, különbenSᵢésTᵢlegyenSésT. - Ha
SᵢvagyTᵢtípusparaméterek, hagyja, hogyS₀ésT₀legyen a tényleges alaposztályuk, különbenS₀ésT₀legyenSᵢésTᵢ.
- Ha
- Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U.- Keresse meg a
D1típuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletS0(haS0osztály vagy struktúra), aS0alaposztályai (haS0osztály),T0(haT0osztály vagy struktúra), valamint aT0alaposztályai (haT0osztály). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U1. Ez a készlet az osztályok vagy struktúrák által aD1-ben deklarált, felhasználó által definiált és emelt implicit vagy explicit konverziós operátorokból áll, amelyek egySáltal magában foglalt vagy körülhatárolt típusból egyTáltal magában foglalt vagy körülhatárolt típussá alakítanak át. - Ha
U1nem üres, akkorUU1. Egyébként- Keresse meg a
D2típuskészletet, amelyből a felhasználó által definiált konverziós operátorok lesznek figyelembe véve. Ez a készletSᵢhatékony illesztőkészletből és azok alapillesztőiből (haSᵢtípusparaméter), valamintTᵢhatékony illesztőkészletből és azok alapillesztőiből (haTᵢtípusparaméter). - Keresse meg a felhasználó által definiált és átvitt konverziós operátorok készletét,
U2. Ez a készlet azD2interfészei által deklarált, felhasználó által definiált, valamint emelt szintű implicit vagy explicit konverziós operátorokból áll, amelyek olyan típusból konvertálnak, amelyet aSmagában foglal vagy rá kiterjed, olyan típusra, amelyet aTmagában foglal vagy rá kiterjed. - Ha
U2nem üres, akkorUU2van.
- Keresse meg a
- Keresse meg a
- Ha
Uüres, a konvertálás nincs meghatározva, és fordítási időhiba lép fel.
Alapértelmezett implementációk
A javaslat további funkciója, hogy lehetővé tegye a statikus virtuális tagok számára a felületeken az alapértelmezett implementációkat, ahogyan a virtuális/absztrakt tagok is teszik.
Az egyik bonyodalom az, hogy az alapértelmezett implementációk más statikus virtuális tagokat "virtuálisan" szeretnének meghívni. Ahhoz, hogy a statikus virtuális tagokat közvetlenül a felületen lehessen meghívni, egy rejtett típusparamétert kell futtatni, amely az aktuális statikus metódus által ténylegesen meghívott "ön" típust jelöli. Ez bonyolultnak, költségesnek és potenciálisan zavarónak tűnik.
Megvitattunk egy egyszerűbb verziót, amely fenntartja a jelenlegi javaslat korlátait: a statikus virtuális tagok csak típusparamétereken hívhatók meg
Úgy döntöttünk https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics, hogy támogatjuk a statikus tagok alapértelmezett implementációit, követve és kibővítve a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md-ben meghatározott szabályokat.
Mintaegyezés
A következő kóddal a felhasználó jogosan várhatja el, hogy a kód 'True'-t nyomtasson (mint ahogy azt tenné, ha az állandó mintát közvetlenül írtuk volna meg).
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));
}
Mivel azonban a minta bemeneti típusa nem double, az állandó 1 minta először ellenőrzi a bejövő T-t int-mal szemben. Ez nem intuitív, ezért blokkolva van, amíg egy jövőbeli C#-verzió nem biztosít jobb kezelést a INumberBase<T>-ból származó típusokkal való numerikus egyeztetéshez. Ehhez azt fogjuk mondani, hogy explicit módon felismerjük INumberBase<T>, mint az a típus, amelyből az összes "szám" származik, és letiltjuk a mintát, ha olyan számtípushoz próbálunk egy numerikus állandó mintát illeszteni, amely nem felel meg a mintának (azaz egy INumberBase<T>-re korlátozott típusparaméter vagy egy INumberBase<T>öröklő felhasználó által definiált számtípus).
Formálisan kivételt adunk az állandó minták esetében a mintával kompatibilis definíciójához.
Az állandó minta egy kifejezés értékét egy állandó értéken teszteli. Az állandó lehet bármilyen állandó kifejezés, például egy konstans, egy deklarált
constváltozó neve vagy enumerálási állandó. Ha a bemeneti érték nem nyílt típus, az állandó kifejezés implicit módon konvertálódik a egyeztetett kifejezés típusára; ha a bemeneti érték típusa nem mintakompatibilis az állandó kifejezés típusával, a mintaegyeztetési művelet hiba. Ha az egyeztetett állandó kifejezés numerikus érték, a bemeneti érték egy olyan típus, amelySystem.Numerics.INumberBase<T>öröklődik, és az állandó kifejezésből a bemeneti érték típusára nincs konstans átalakítás, a mintaegyeztetési művelet hiba.
A relációs mintákhoz hasonló kivételt is hozzáadunk:
Amikor a bemenet egy olyan típus, amelyhez létezik egy megfelelő beépített bináris relációs operátor, és a bemenet a bal oldali operandusként, az adott állandó pedig a jobb oldali operandusként alkalmazandó, akkor az operátor kiértékelését értelmezzük a relációs minta jelentéseként. Ellenkező esetben a bemenetet explicit null értékű vagy nemboxolásos átalakítással alakítjuk át a kifejezés típusára. Fordítási idejű hiba, ha nincs ilyen átalakítás. Fordítási idejű hiba, ha a bemeneti típus egy olyan típusparaméter, amely
System.Numerics.INumberBase<T>-ra van korlátozva, vagy egySystem.Numerics.INumberBase<T>-típusból örököl, és a bemeneti típusnál nincsen definiálva megfelelő beépített bináris relációs operátor. A mintázat nem tekintendő egyezőnek, ha az átalakítás sikertelen. Ha az átalakítás sikeres, akkor a mintaegyeztetési művelet eredménye az e OP v kifejezés kiértékelésének eredménye, ahol az e az átalakított bemenet, az OP a relációs operátor, a v pedig az állandó kifejezés.
Hátránya
- A "statikus absztrakció" egy új fogalom, amely értelmesen növeli a C#elméleti terhelését.
- Nem olcsó funkció megépíteni. Meg kell győződnünk róla, hogy megéri.
Alternatívák
Szerkezeti korlátozások
Egy másik megközelítés az lenne, ha "strukturális korlátozások" lennének közvetlenül, és explicit módon megkövetelnék adott operátorok jelenlétét egy típusparaméteren. Ennek hátrányai: - Ezt minden alkalommal ki kell írni. A névvel ellátott kényszer jobb választásnak tűnik. - Ez egy teljesen új típusú kényszer, míg a javasolt funkció a felületi korlátozások meglévő fogalmát használja. - Ez csak az operátoroknál működik, más típusú statikus tagoknál viszont nem (könnyen).
Megoldatlan kérdések
Statikus absztrakciós felületek és statikus osztályok
További információért tekintse meg a https://github.com/dotnet/csharplang/issues/5783 és a https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes.
Tervezési értekezletek
- 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