Statyczne abstrakcyjne członki w interfejsach
Nota
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Problem z czempionem: https://github.com/dotnet/csharplang/issues/4436
Streszczenie
Interfejs może określać abstrakcyjne składowe statyczne, które klasy i struktury implementujące muszą następnie zaimplementować jawnie lub niejawnie. Dostęp do członków można uzyskać poprzez parametry typu, które są ograniczone przez interfejs.
Motywacja
Obecnie nie ma możliwości abstrakcjonowania statycznych elementów członkowskich i pisania uogólnionego kodu, który ma zastosowanie w różnych typach definiujących te statyczne elementy członkowskie. Jest to szczególnie problematyczne w przypadku rodzajów składowych, które tylko istnieją w postaci statycznej, zwłaszcza operatory.
Ta funkcja umożliwia ogólne algorytmy dla typów liczbowych reprezentowane przez ograniczenia interfejsu określające obecność danych operatorów. W związku z tym algorytmy można wyrazić pod względem takich operatorów:
// 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 });
Składnia
Składniki interfejsu
Funkcja umożliwi zadeklarowanie wirtualnych statycznych członków interfejsu.
Reguły przed C# 11
Przed C# 11 członkowie instancji w interfejsach są domyślnie abstrakcyjni (lub wirtualni, jeśli mają domyślną implementację), ale opcjonalnie mogą mieć modyfikator abstract
(lub virtual
). Członkowie instancji niewirtualnych muszą być jawnie oznaczeni jako sealed
.
Statyczne elementy członkowskie interfejsu są obecnie niejawnie niewirtualne i nie zezwalają na modyfikatory abstract
, virtual
ani sealed
.
Propozycja
Abstrakcyjne statyczne członkowie
Statyczne elementy członkowskie interfejsu inne niż pola mogą również mieć modyfikator abstract
. Abstrakcyjne statyczne elementy członkowskie nie mogą mieć ciała (a w przypadku właściwości akcesory nie mogą mieć ciała).
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);
}
Wirtualne statyczne członkowie
Członkowie interfejsu statycznego, inni niż pola, mogą również mieć modyfikator virtual
. Wirtualni statyczni członkowie muszą mieć ciało.
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(); }
}
Jawnie niewirtualne statyczne elementy członkowskie
W celu zapewnienia symetrii z elementami członkowskimi instancji niewirtualnymi, statyczne elementy członkowskie (z wyjątkiem pól) powinny mieć możliwość użycia opcjonalnego modyfikatora sealed
, mimo że z natury są niewirtualne.
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;
}
Implementacja elementów członkowskich interfejsu
Dzisiejsze reguły
Klasy i struktury mogą implementować abstrakcyjne elementy członkowskie wystąpień interfejsów niejawnie lub jawnie. Niejawnie zaimplementowana składowa interfejsu jest normalną (wirtualną lub niewirtualną) deklaracją składowej klasy lub struktury, która po prostu przypadkiem przy okazji zaimplementuje składową interfejsu. Składnik może nawet zostać odziedziczony z klasy bazowej, a przez to nie być obecnym w deklaracji klasy.
Jawnie zaimplementowany element członkowski interfejsu używa kwalifikowanej nazwy do identyfikowania danego elementu członkowskiego interfejsu. Implementacja nie jest bezpośrednio dostępna jako element w klasie lub strukturze, ale tylko za pośrednictwem interfejsu.
Propozycja
W klasach i strukturach nie jest wymagana żadna nowa składnia, aby ułatwić niejawną implementację statycznych składowych interfejsu abstrakcyjnego. Istniejące deklaracje statycznych składowych służą do tego celu.
Jawne implementacje statycznych abstrakcyjnych elementów członkowskich interfejsu używają kwalifikowanej nazwy wraz z modyfikatorem 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;
}
Semantyka
Ograniczenia operatora
Obecnie wszystkie deklaracje operatorów jednoargumentowych i binarnych mają wymagania dotyczące tego, że co najmniej jeden z ich operandów musi być typu T
lub T?
, gdzie T
jest typem wystąpienia otaczającego typu.
Te wymagania muszą zostać złagodzone, aby ograniczony operand mógł być parametrem typu, który jest liczony jako "typ wystąpienia otaczającego typu".
Aby parametr typu T
mógł być uznany za "typ wystąpienia tych, które go otaczają", musi spełniać następujące wymagania:
-
T
jest bezpośrednim parametrem typu w interfejsie, w którym występuje deklaracja operatora i -
T
jest bezpośrednio ograniczone przez to, co specyfikacja nazywa "instance type" — tj. interfejs otaczający z własnymi parametrami typu używanymi jako argumenty typu.
Operatory równości i konwersje
Abstrakcyjne/wirtualne deklaracje operatorów ==
i !=
, a także abstrakcyjne/wirtualne deklaracje niejawnych i jawnych operatorów konwersji będą dozwolone w interfejsach. Interfejsy pochodne również będą mogły je implementować.
Dla operatorów ==
i !=
co najmniej jeden typ parametru musi być parametrem typu, który jest uznawany za "typ wystąpienia obejmującego typu", zgodnie z definicją w poprzedniej sekcji.
Implementowanie statycznych elementów abstrakcyjnych
Reguły dotyczące tego, kiedy statyczna deklaracja członka w klasie lub strukturze jest uznawana za implementację statycznego abstrakcyjnego członka interfejsu, oraz jakie wymagania mają zastosowanie, gdy tak się dzieje, są takie same jak w przypadku członków instancji.
TBD: W tym miejscu mogą istnieć dodatkowe lub różne reguły, o których jeszcze nie myśleliśmy.
Interfejsy jako argumenty typu
Omówiliśmy problem zgłoszony przez https://github.com/dotnet/csharplang/issues/5955 i postanowiliśmy dodać ograniczenie dotyczące użycia interfejsu jako argumentu typu (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts). Oto ograniczenie, które zostało zaproponowane przez https://github.com/dotnet/csharplang/issues/5955 i zatwierdzone przez LDM.
Interfejs zawierający lub dziedziczący statyczny element członkowski abstrakcyjny/wirtualny, który nie ma najbardziej konkretnej implementacji w interfejsie, nie może być używany jako argument typu. Jeśli wszystkie statyczne elementy członkowskie abstrakcyjne/wirtualne mają najbardziej określoną implementację, interfejs może być używany jako argument typu.
Uzyskiwanie dostępu do statycznych składowych interfejsu abstrakcyjnego
Dostęp do statycznego elementu członkowskiego interfejsu abstrakcyjnego M
można uzyskać na parametrze typu T
przy użyciu wyrażenia T.M
, gdy T
jest ograniczany przez interfejs I
, a M
jest dostępnym statycznym abstrakcyjnym elementem członkowskim I
.
T M<T>() where T : I<T>
{
T.M();
T t = T.P;
T.E += () => { };
return t + T.P;
}
W czasie wykonywania wykorzystywana jest rzeczywista implementacja elementu, która istnieje w rzeczywistym typie podanym jako argument typu.
C c = M<C>(); // The static members of C get called
Ponieważ wyrażenia zapytania są określone jako składnia ponownego zapisywania, język C# umożliwia użycie typu jako źródła zapytania, o ile ma statyczne elementy członkowskie dla używanych operatorów zapytań. Innymi słowy, jeśli składnia pasuje, pozwalamy na to! Uważamy, że to zachowanie nie było zamierzone ani istotne w pierwotnej wersji LINQ i nie chcemy pracować, aby je wspierać na parametrach typu. Jeśli istnieją tam scenariusze, usłyszymy o nich i możemy zdecydować się na to później.
Bezpieczeństwo wariancji §18.2.3.2
Reguły bezpieczeństwa wariancji powinny mieć zastosowanie do podpisów statycznych abstrakcyjnych elementów członkowskich. Należy dostosować dodatek proponowany w https://github.com/dotnet/csharplang/blob/main/proposals/variance-safety-for-static-interface-members.md#variance-safety
Te ograniczenia nie mają zastosowania do wystąpień typów w deklaracjach statycznych elementów członkowskich.
do
Te ograniczenia nie mają zastosowania do wystąpień typów w deklaracjach niewirtualnych, nie abstrakcyjnych statycznych elementów członkowskich.
§10.5.4 Konwersje niejawne zdefiniowane przez użytkownika
Następujące punkty punktorowe
- Określ typy
S
,S₀
iT₀
.- Jeśli
E
ma typ, to niechS
będzie tym typem. - Jeśli
S
lubT
są typami dopuszczającymi wartość null, niechSᵢ
iTᵢ
będą ich typami bazowymi, w przeciwnym razie niechSᵢ
iTᵢ
będą odpowiednioS
iT
. - Jeśli
Sᵢ
lubTᵢ
są parametrami typu, niechS₀
iT₀
będą ich efektywnymi klasami bazowymi, w przeciwnym razie niechS₀
iT₀
będą odpowiednioSₓ
iTᵢ
.
- Jeśli
- Znajdź zestaw typów,
D
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zS0
(jeśliS0
jest klasą lub strukturą), klasy bazoweS0
(jeśliS0
jest klasą) iT0
(jeśliT0
jest klasą lub strukturą). - Znajdź zestaw odpowiednich operatorów konwersji, zdefiniowanych przez użytkownika i podniesionych,
U
. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych operatorów konwersji zadeklarowanych przez klasy lub struktury wD
, które konwertują z typu obejmującegoS
do typu objętegoT
. JeśliU
jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.
są dostosowywane w następujący sposób:
- Określ typy
S
,S₀
iT₀
.- Jeśli
E
ma typ, przypiszS
ten typ. - Jeśli
S
lubT
są typami wartości dopuszczającymi wartość null, niechSᵢ
iTᵢ
będą ich typami bazowymi, w przeciwnym razie niechSᵢ
iTᵢ
będą odpowiednioS
iT
. - Jeśli
Sᵢ
lubTᵢ
są parametrami typu, niechS₀
iT₀
będą ich efektywnymi klasami bazowymi, w przeciwnym razie niechS₀
iT₀
będą odpowiednioSₓ
iTᵢ
.
- Jeśli
- Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika oraz operatorów podniesionych,
U
.- Znajdź zestaw typów,
D1
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zS0
(jeśliS0
jest klasą lub strukturą), klasy bazoweS0
(jeśliS0
jest klasą) iT0
(jeśliT0
jest klasą lub strukturą). - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych,
U1
. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych operatorów konwersji zadeklarowanych przez klasy lub struktury wD1
, które konwertują z typu obejmującegoS
do typu objętegoT
. - Jeśli
U1
nie jest pusta,U
jestU1
. Inaczej- Znajdź zestaw typów,
D2
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zSᵢ
efektywnego zestawu interfejsów i ich interfejsów podstawowych (jeśliSᵢ
jest parametrem typu), aTᵢ
efektywny zestaw interfejsów (jeśliTᵢ
jest parametrem typu). - Znajdź zestaw odpowiednich zdefiniowanych przez użytkownika i podniesionych operatorów konwersji,
U2
. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych operatorów konwersji zadeklarowanych przez interfejsy wD2
, które konwertują z typu obejmującegoS
do typu objętegoT
. - Jeśli
U2
nie jest pusta,U
jestU2
- Znajdź zestaw typów,
- Znajdź zestaw typów,
- Jeśli
U
jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.
§10.3.9 jawne konwersje zdefiniowane przez użytkownika
Następujące punkty punktorowe
- Określ typy
S
,S₀
iT₀
.- Jeśli
E
ma typ, niechS
będzie tym typem. - Jeśli
S
lubT
są typami wartościowymi dopuszczającymi wartość null, niechSᵢ
iTᵢ
będą ich typami bazowymi, w przeciwnym razie niechSᵢ
iTᵢ
będą odpowiednioS
iT
. - Jeśli
Sᵢ
lubTᵢ
są parametrami typu, niechS₀
iT₀
będą ich efektywnymi klasami bazowymi, w przeciwnym razie niechS₀
iT₀
będą odpowiednioSᵢ
iTᵢ
.
- Jeśli
- Znajdź zestaw typów,
D
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zS0
(jeśliS0
jest klasą lub strukturą), klasy bazoweS0
(jeśliS0
jest klasą),T0
(jeśliT0
jest klasą lub strukturą), a klasy bazoweT0
(jeśliT0
jest klasą). - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych,
U
. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych lub jawnych operatorów konwersji zadeklarowanych przez klasy lub struktury wD
, które konwertują z typu obejmującego lub objętego przezS
na typ obejmujący lub objęty przezT
. JeśliU
jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.
są dostosowywane w następujący sposób:
- Określ typy
S
,S₀
iT₀
.- Jeśli
E
ma typ, niechS
będzie tym typem. - Jeśli
S
lubT
są typami wartości dopuszczającymi null, niechSᵢ
iTᵢ
będą ich typami bazowymi, w przeciwnym razie niechSᵢ
iTᵢ
będą odpowiednioS
iT
. - Jeśli
Sᵢ
lubTᵢ
są parametrami typu, niechS₀
iT₀
będą ich efektywnymi klasami bazowymi, w przeciwnym razie niechS₀
iT₀
będą odpowiednioSᵢ
iTᵢ
.
- Jeśli
- Znajdź zestaw odpowiednich zdefiniowanych przez użytkownika i podniesionych operatorów konwersji,
U
.- Znajdź zestaw typów,
D1
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zS0
(jeśliS0
jest klasą lub strukturą), klasy bazoweS0
(jeśliS0
jest klasą),T0
(jeśliT0
jest klasą lub strukturą), a klasy bazoweT0
(jeśliT0
jest klasą). - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i zniesionych,
U1
. Ten zbiór składa się z zdefiniowanych przez użytkownika i podniesionych do rangi niejawnych lub jawnych operatorów konwersji zadeklarowanych przez klasy lub struktury wD1
, które konwertują z typu obejmującego lub objętego przezS
na typ obejmujący lub objęty przezT
. - Jeśli
U1
nie jest pusta,U
jestU1
. Inaczej- Znajdź zestaw typów,
D2
, z którego zostaną uwzględnione operatory konwersji zdefiniowane przez użytkownika. Ten zestaw składa się zSᵢ
efektywnego zestawu interfejsów i ich interfejsów podstawowych (jeśliSᵢ
jest parametrem typu), aTᵢ
efektywny zestaw interfejsów i ich interfejsów podstawowych (jeśliTᵢ
jest parametrem typu). - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych,
U2
. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych lub jawnych operatorów konwersji zadeklarowanych przez interfejsy wD2
, które konwertują z typu obejmowanego lub obejmującegoS
do typu obejmowanego lub obejmującego przezT
. - Jeśli
U2
nie jest pusta,U
jestU2
- Znajdź zestaw typów,
- Znajdź zestaw typów,
- Jeśli
U
jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.
Implementacje domyślne
dodatkową funkcją dla tej propozycji jest umożliwienie statycznym elementom wirtualnym w interfejsach domyślnych implementacji, podobnie jak elementy członkowskie wirtualne/abstrakcyjne wystąpienia.
Jedną z komplikacji jest to, że domyślne implementacje powinny wywoływać inne statyczne wirtualne elementy członkowskie w sposób "wirtualny". Zezwolenie na bezpośrednie wywoływanie członków statycznych, które są wirtualne, na poziomie interfejsu wymagałoby przekazywania ukrytego parametru typu reprezentującego typ "self", na którym rzeczywiście wywołano aktualną metodę statyczną. Wydaje się to skomplikowane, kosztowne i potencjalnie mylące.
Omówiliśmy prostszą wersję, która utrzymuje ograniczenia bieżącej propozycji, że statyczne wirtualne elementy członkowskie mogą wywoływane tylko na parametry typu. Ponieważ interfejsy ze statycznymi członkami wirtualnymi często mają jawny parametr typu reprezentujący typ "self", nie byłoby to dużą stratą: inne statyczne członki wirtualne mogą być po prostu wywoływane dla tego typu "self". Ta wersja jest o wiele prostsza i wydaje się całkiem do zrobienia.
W https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics postanowiliśmy wspierać domyślne implementacje statycznych elementów członkowskich, dostosowując się i rozwijając reguły ustanowione w https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md.
Dopasowywanie wzorca
Biorąc pod uwagę następujący kod, użytkownik może rozsądnie oczekiwać, że wyświetli "True" (tak, jakby wzorzec stałej został zapisany w kodzie źródłowym):
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));
}
Jednakże, ponieważ typ danych wejściowych wzorca nie jest double
, stały wzorzec 1
najpierw sprawdzi nadchodzące T
względem int
. Jest to nieintuicyjne, dlatego jest blokowane do czasu, aż przyszła wersja języka C# doda lepszą obsługę dopasowywania liczbowego do typów pochodnych od INumberBase<T>
. W tym celu powiemy, że jawnie rozpoznamy INumberBase<T>
jako typ, z którego pochodzą wszystkie "liczby", i zablokujemy wzorzec, jeśli próbujemy dopasować wzorzec stałej liczbowej do typu liczbowego, w jakim nie możemy reprezentować wzorca (tj. parametr typu ograniczony do INumberBase<T>
lub typu liczbowego zdefiniowanego przez użytkownika, który dziedziczy z INumberBase<T>
).
Formalnie dodamy wyjątek do definicji zgodnej ze wzorcem dla wzorców stałych:
Stały wzorzec sprawdza wartość wyrażenia względem stałej wartości. Stała może być dowolnym wyrażeniem stałym, takim jak literał, nazwa zadeklarowanej zmiennej
const
lub stała wyliczenia. Jeśli wartość wejściowa nie jest typem otwartym, wyrażenie stałe jest niejawnie konwertowane na typ dopasowanego wyrażenia; Jeśli typ wartości wejściowej nie jest zgodny ze wzorcem z typem wyrażenia stałego, operacja dopasowywania wzorca jest błędem. Jeśli wyrażenie stałe, które jest dopasowywane, jest wartością liczbową, a wartość wejściowa jest typem, który dziedziczy zSystem.Numerics.INumberBase<T>
, i brak jest możliwości stałej konwersji wyrażenia stałego do typu wartości wejściowej, operacja dopasowywania wzorca jest błędem.
Dodamy również podobny wyjątek dla wzorców relacyjnych:
Gdy wejście jest typem, dla którego zdefiniowano odpowiedni wbudowany binarny operator relacyjny, stosowany z wejściem jako lewym operandem, a stałą jako prawym operandem, wynik działania tego operatora jest przyjmowany jako znaczenie wzorca relacyjnego. W przeciwnym razie, konwertujemy dane wejściowe do typu wyrażenia przy użyciu jawnej konwersji do typu dopuszczającego null lub rozpakowującej. Jest to błąd czasu kompilacji, jeśli taka konwersja nie istnieje. Jest to błąd czasu kompilacji, jeśli typ wejściowy jest ograniczony przez parametr typu lub dziedziczący z
System.Numerics.INumberBase<T>
, a typ wejściowy nie ma wbudowanego odpowiedniego operatora relacyjnego binarnego. Wzorzec jest uznawany za niezgodny, jeśli konwersja nie powiedzie się. Jeśli konwersja powiedzie się, wynikiem operacji dopasowywania wzorca jest ocena wyrażenia e OP v, gdzie e jest przekonwertowanym wejściem, OP jest operatorem relacyjnym, a v jest wyrażeniem stałym.
Wady
- "Statyczna abstrakcja" to nowa koncepcja, która znacząco wpłynie na zwiększenie koncepcyjnego obciążenia języka C#.
- Nie jest to tania funkcja do zbudowania. Powinniśmy upewnić się, że warto.
Alternatywy
Ograniczenia strukturalne
Alternatywną metodą byłoby bezpośrednie "ograniczenia strukturalne" i jawne wymaganie obecności określonych operatorów na parametrze typu. Wady tego są: - To musiałoby być zapisywane za każdym razem. Posiadanie nazwanego ograniczenia wydaje się lepsze. - Jest to zupełnie nowy rodzaj ograniczenia, natomiast proponowana funkcja wykorzystuje istniejącą koncepcję ograniczeń interfejsu. - Działałoby to tylko dla operatorów, a inne rodzaje statycznych elementów członkowskich byłyby trudne do wdrożenia.
Nierozwiązane pytania
Statyczne interfejsy abstrakcyjne i klasy statyczne
Aby uzyskać więcej informacji, zobacz https://github.com/dotnet/csharplang/issues/5783 i https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes.
Spotkania projektowe
- 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