Priorytet rozwiązywania przeciążenia
Notatka
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ą ujęte w odpowiednich notatkach z zebrania dotyczącego języka projektowania (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/7706
Streszczenie
Wprowadzamy nowy atrybut, System.Runtime.CompilerServices.OverloadResolutionPriority
, który może być używany przez autorów interfejsów API do dostosowywania względnego priorytetu przeciążeń w ramach jednego typu jako środka sterującego użytkownikami interfejsu API do używania określonych interfejsów API, nawet jeśli te interfejsy API byłyby zwykle uważane za niejednoznaczne lub w inny sposób nie są wybierane przez reguły rozpoznawania przeciążeń języka C#.
Motywacja
Autorzy interfejsów API często napotykają problem, co zrobić z elementem po jego oznaczeniu jako przestarzały. W celach zgodności wstecznej wiele osób zachowa istniejący element ze zmienną ObsoleteAttribute
ustawioną na błąd na stałe, aby uniknąć zakłóceń u użytkowników, którzy uaktualnią pliki binarne podczas działania. Dotyczy to szczególnie systemów wtyczek, w których autor wtyczki nie kontroluje środowiska, w którym działa wtyczka. Twórca środowiska może chcieć zachować starszą metodę, ale zablokować dostęp do niego dla dowolnego nowo utworzonego kodu. Jednak ObsoleteAttribute
sama w sobie nie wystarczy. Typ lub element członkowski jest nadal widoczny w rozwiązywaniu przeciążeń i może powodować niepożądane błędy rozwiązywania przeciążeń, gdy istnieje zupełnie dobra alternatywa, ale ta alternatywa jest albo niejednoznaczna z przestarzałym elementem członkowskim, lub obecność przestarzałego elementu członkowskiego powoduje zakończenie rozpoznawania przeciążenia na początku, nie biorąc pod uwagę dobrego elementu członkowskiego. W tym celu chcemy, aby autorzy interfejsów API mogli kierować się rozpoznawaniem przeciążeń w zakresie rozwiązywania niejednoznaczności, aby mogli rozwijać obszary powierzchni interfejsu API i kierować użytkowników w kierunku wydajnych interfejsów API bez konieczności naruszenia bezpieczeństwa środowiska użytkownika.
Zespół bibliotek klas bazowych (BCL) ma kilka przykładów, w których może to okazać się przydatne. Niektóre (hipotetyczne) przykłady to:
- Tworzenie przeciążenia
Debug.Assert
, które używaCallerArgumentExpression
do uzyskania wyrażenia do załączenia w komunikacie, dzięki czemu będzie ono preferowane od istniejącego przeciążenia. - Preferowanie
string.IndexOf(string, StringComparison = Ordinal)
nadstring.IndexOf(string)
. Trzeba by to omówić jako potencjalną zmianę mogącą powodować niezgodność, ale istnieje pogląd, że to lepsza wartość domyślna i bardziej prawdopodobne, że tego właśnie chciał użytkownik. - Połączenie tej propozycji i
CallerAssemblyAttribute
pozwoliłoby metodom, które mają niejawną tożsamość wywołującego, uniknąć kosztownych przechodzeń po stosie.Assembly.Load(AssemblyName)
robi to dzisiaj i mogłoby być to o wiele bardziej wydajne. -
Microsoft.Extensions.Primitives.StringValues
uwidacznia niejawną konwersję nastring
istring[]
. Oznacza to, że przekazany element jest niejednoznaczny w przypadku zastosowania metody z przeciążeniamiparams string[]
iparams ReadOnlySpan<string>
. Ten atrybut może służyć do określania priorytetów jednego z przeciążeń, aby zapobiec niejednoznaczności.
Szczegółowy projekt
Priorytet rozwiązania przeciążenia
Definiujemy nowe pojęcie, overload_resolution_priority, które jest używane podczas procesu rozwiązywania grupy metod.
overload_resolution_priority jest 32-bitową wartością całkowitą. Domyślnie wszystkie metody mają overload_resolution_priority 0 i można to zmienić, stosując OverloadResolutionPriorityAttribute
do metody. Aktualizujemy sekcję §12.6.4.1 specyfikacji języka C# w następujący sposób (zmiana pogrubiona ):
Po zidentyfikowaniu członków funkcji kandydatów i listy argumentów, wybór najlepszego członka funkcji jest taki sam we wszystkich przypadkach:
- Po pierwsze, zestaw funkcji kandydujących jest zawężony do tych, które są stosowne w kontekście danej listy argumentów (§12.6.4.2). Jeśli ten ograniczony zestaw jest pusty, wystąpi błąd czasu kompilacji.
- Następnie zmniejszony zestaw kandydatów na członków jest grupowany według typu deklaracji. W każdej grupie:
- Członkowskie elementy funkcji kandydującej są uporządkowane według priorytetu rozstrzygania przeciążeń . Jeśli element członkowski jest przesłanianiem, overload_resolution_priority pochodzi z najmniej pochodnego deklaracji tego elementu członkowskiego.
- Wszystkie elementy członkowskie, które mają niższą overload_resolution_priority niż najwyższa znaleziona w ramach deklarowanej grupy typów, zostaną usunięte.
- Grupy zredukowane zostaną następnie ponownie uwzględnione w ostatnim zestawie odpowiednich członków funkcji kandydata.
- Następnie znajduje się najlepszy członek funkcji z zestawu możliwych funkcji kandydujących. Jeśli zestaw zawiera tylko jedną funkcję, to ta funkcja jest najlepszą. W przeciwnym razie najlepszym elementem członkowskim funkcji jest jeden element członkowski funkcji, który jest lepszy niż wszystkie inne elementy członkowskie funkcji w odniesieniu do danej listy argumentów, pod warunkiem, że każdy element członkowski funkcji jest porównywany ze wszystkimi innymi elementami członkowskimi funkcji przy użyciu reguł w §12.6.4.3. Jeśli nie ma dokładnie jednego elementu funkcji, który jest lepszy niż wszystkie inne elementy funkcji, wywołanie składowej funkcji jest niejednoznaczne i pojawia się błąd czasu powiązania.
Na przykład ta funkcja spowoduje, że poniższy fragment kodu wyświetli "Span", a nie "Array".
using System.Runtime.CompilerServices;
var d = new C1();
int[] arr = [1, 2, 3];
d.M(arr); // Prints "Span"
class C1
{
[OverloadResolutionPriority(1)]
public void M(ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority
public void M(int[] a) => Console.WriteLine("Array");
}
Efektem tej zmiany jest to, że podobnie jak przycinanie dla większości typów pochodnych, dodajemy końcowe przycinanie dla priorytetu rozwiązywania przeciążeń. Ponieważ to przycinanie występuje w samym końcowym etapie procesu rozwiązywania przeciążenia, oznacza to, że typ podstawowy nie może zapewnić, że jego składowe mają wyższy priorytet niż typ pochodny. Jest to zamierzone i zapobiega występowaniu wyścigu zbrojeń, w którym typ podstawowy może starać się zawsze być lepszy niż typ pochodny. Na przykład:
using System.Runtime.CompilerServices;
var d = new Derived();
d.M([1, 2, 3]); // Prints "Derived", because members from Base are not considered due to finding an applicable member in Derived
class Base
{
[OverloadResolutionPriority(1)]
public void M(ReadOnlySpan<int> s) => Console.WriteLine("Base");
}
class Derived : Base
{
public void M(int[] a) => Console.WriteLine("Derived");
}
Liczby ujemne mogą być używane i mogą służyć do oznaczania określonego przeciążenia jako gorszego niż wszystkie inne przeciążenia domyślne.
overload_resolution_priority elementu członkowskiego pochodzi z najmniej pochodnej deklaracji tego elementu członkowskiego.
overload_resolution_priority nie jest dziedziczone ani wywnioskowane z żadnych elementów członkowskich interfejsu, które mogą być implementowane przez członka typu, i biorąc pod uwagę Mx
członka typu, który implementuje Mi
interfejsu, nie pojawia się ostrzeżenie, jeśli Mx
i Mi
mają różne overload_resolution_priorities.
NB: Celem tej reguły jest replikowanie zachowania modyfikatora
params
.
System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute
Wprowadzamy następujący atrybut do BCL:
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class OverloadResolutionPriorityAttribute(int priority) : Attribute
{
public int Priority => priority;
}
Wszystkie metody w języku C# mają domyślną priorytet rozstrzygania przeciążenia o wartości 0, chyba że mają przypisaną cechę OverloadResolutionPriorityAttribute
. Jeśli mają przypisany ten atrybut, wtedy ich overload_resolution_priority jest wartością liczbową całkowitą podaną jako pierwszy argument atrybutu.
Jest to błąd podczas stosowania OverloadResolutionPriorityAttribute
do następujących lokalizacji:
- Właściwości nieindeksatora
- Metody dostępu do właściwości, indeksatora lub zdarzenia
- Operatory konwersji
- Lambdas
- Funkcje lokalne
- Finalizatory
- Konstruktory statyczne
Atrybuty napotkane w tych lokalizacjach w metadanych są ignorowane przez język C#.
Błędem jest stosowanie OverloadResolutionPriorityAttribute
w miejscu, gdzie byłby ignorowany, na przykład przy przesłanianiu metody podstawowej, ponieważ priorytet jest odczytywany z najmniej rozwiniętej deklaracji członka.
NB: Celowo różni się to od zachowania modyfikatora
params
, który umożliwia ponowne określenie lub dodanie w sytuacji pominięcia.
Możliwość wywoływania członków
Ważnym zastrzeżeniem dla OverloadResolutionPriorityAttribute
jest to, że może to sprawić, że niektórzy członkowie będą skutecznie niemożliwi do wywołania z źródła. Na przykład:
using System.Runtime.CompilerServices;
int i = 1;
var c = new C3();
c.M1(i); // Will call C3.M1(long), even though there's an identity conversion for M1(int)
c.M2(i); // Will call C3.M2(int, string), even though C3.M1(int) has less default parameters
class C3
{
public void M1(int i) {}
[OverloadResolutionPriority(1)]
public void M1(long l) {}
[Conditional("DEBUG")]
public void M2(int i) {}
[OverloadResolutionPriority(1), Conditional("DEBUG")]
public void M2(int i, [CallerArgumentExpression(nameof(i))] string s = "") {}
public void M3(string s) {}
[OverloadResolutionPriority(1)]
public void M3(object o) {}
}
W tych przykładach domyślne przeciążenia priorytetu skutecznie stają się szczątkowe i można je wywoływać tylko za pomocą kilku kroków, które wymagają dodatkowego wysiłku.
- Konwertowanie metody na delegata, a następnie używanie tego delegata.
- W przypadku niektórych scenariuszy wariancji typu referencyjnego, takich jak
M3(object)
, który ma priorytet nadM3(string)
, ta strategia zakończy się niepowodzeniem. - Metody warunkowe, takie jak
M2
, również nie mogą być wywoływane przy użyciu tej strategii, ponieważ metody warunkowe nie mogą być konwertowane na delegaty.
- W przypadku niektórych scenariuszy wariancji typu referencyjnego, takich jak
- Używanie funkcji środowiska uruchomieniowego
UnsafeAccessor
do jej wywołania przy użyciu zgodnego podpisu. - Ręczne używanie mechanizmu refleksji w celu uzyskania odwołania do metody, a następnie jej wywołania.
- Kod, który nie jest ponownie skompilowany, będzie nadal wywoływać stare metody.
- Odręczny IL może określić cokolwiek zechce.
Otwórz pytania
Grupowanie metod rozszerzenia (udzielono odpowiedzi)
Obecnie metody rozszerzeń są uporządkowane według priorytetu tylko w ramach własnego typu. Na przykład:
new C2().M([1, 2, 3]); // Will print Ext2 ReadOnlySpan
static class Ext1
{
[OverloadResolutionPriority(1)]
public static void M(this C2 c, Span<int> s) => Console.WriteLine("Ext1 Span");
[OverloadResolutionPriority(0)]
public static void M(this C2 c, ReadOnlySpan<int> s) => Console.WriteLine("Ext1 ReadOnlySpan");
}
static class Ext2
{
[OverloadResolutionPriority(0)]
public static void M(this C2 c, ReadOnlySpan<int> s) => Console.WriteLine("Ext2 ReadOnlySpan");
}
class C2 {}
Czy podczas rozwiązywania przeciążeń dla elementów członkowskich rozszerzenia nie powinniśmy sortować według deklarowanego typu, a zamiast tego rozważyć wszystkie rozszerzenia w tym samym zakresie?
Odpowiedź
Zawsze będziemy grupować. Powyższy przykład spowoduje wyświetlenie Ext2 ReadOnlySpan
Dziedziczenie atrybutów w przypadku nadpisania (udzielono odpowiedzi)
Czy atrybut powinien być dziedziczony? Jeśli nie, jaki jest priorytet zastępującego elementu członkowskiego?
Jeśli atrybut jest określony na wirtualnym elemencie członkowskim, czy należy zastąpić ten element członkowski, aby powtórzyć atrybut?
Odpowiedź
Atrybut nie zostanie oznaczony jako dziedziczony. Przyjrzymy się deklaracji członka o najmniejszym stopniu pochodzenia, aby określić jego priorytet w rozwiązywaniu przeciążeń.
Błąd aplikacji lub ostrzeżenie dotyczące nadpisania (odpowiedziane)
class Base
{
[OverloadResolutionPriority(1)] public virtual void M() {}
}
class Derived
{
[OverloadResolutionPriority(2)] public override void M() {} // Warn or error for the useless and ignored attribute?
}
Co należy zrobić w przypadku stosowania OverloadResolutionPriorityAttribute
w kontekście, w którym jest ignorowany, tak jak przesłonięcie:
- Nic nie rób, niech dyskretnie zostanie zignorowany.
- Wydaj ostrzeżenie, że atrybut zostanie zignorowany.
- Zgłoś błąd, że atrybut jest niedozwolony.
3 jest najbardziej ostrożnym podejściem, jeśli uważamy, że w przyszłości może istnieć miejsce, w którym możemy zezwolić na nadpisanie, aby określić ten atrybut.
Odpowiedź
Będziemy używać wartości 3 i zablokujemy aplikację w lokalizacjach, w których zostałaby zignorowana.
Niejawna implementacja interfejsu (odpowiedź)
Jakie powinno być zachowanie niejawnej implementacji interfejsu? Czy należy określić OverloadResolutionPriority
? Jakie zachowanie kompilatora powinno być w przypadku napotkania niejawnej implementacji bez priorytetu? Będzie to prawie z pewnością się zdarzyć, ponieważ biblioteka interfejsu może zostać zaktualizowana, ale nie implementacja. Poprzedni stan techniki tutaj z params
nie określa się i nie przenosi wartości.
using System;
var c = new C();
c.M(1, 2, 3); // error CS1501: No overload for method 'M' takes 3 arguments
((I)c).M(1, 2, 3);
interface I
{
void M(params int[] ints);
}
class C : I
{
public void M(int[] ints) { Console.WriteLine("params"); }
}
Dostępne są następujące opcje:
- Postępuj zgodnie z
params
.OverloadResolutionPriorityAttribute
nie będzie przenoszona domyślnie ani wymagana do określenia. - Przenieś atrybut niejawnie.
- Nie przenoś atrybutu niejawnie, należy go określić w miejscu wywołania.
- Powoduje to dodatkowe pytanie: jakie zachowanie powinno być, gdy kompilator napotka ten scenariusz z skompilowanymi odwołaniami?
Odpowiedź
Pójdziemy z 1.
Dalsze błędy aplikacji (udzielono odpowiedzi)
Istnieje kilka innych lokalizacji, takich jak tym, które należy potwierdzić. Obejmują one:
- Operatory konwersji — specyfikacja nigdy nie mówi, że operatory konwersji przechodzą przez rozpoznawanie przeciążeń, więc implementacja blokuje aplikację na tych elementach członkowskich. Czy należy to potwierdzić?
- Lambdas — podobnie lambdy nigdy nie podlegają rozwiązywaniu przeciążeń, więc implementacja je blokuje. Czy należy to potwierdzić?
- Destruktory — ponownie, obecnie zablokowane.
- Konstruktory statyczne — obecnie ponownie zablokowane.
- Funkcje lokalne — nie są one obecnie blokowane, ponieważ przechodzą rozpoznawanie przeciążenia, po prostu nie można ich przeciążyć. Jest to podobne do sytuacji, w której nie popełniamy błędu, gdy atrybut jest stosowany do członka typu, który nie jest przeciążony. Czy to zachowanie powinno zostać potwierdzone?
Odpowiedź
Wszystkie wymienione powyżej lokalizacje są blokowane.
Zachowanie langversion (udzielono odpowiedzi)
Implementacja obecnie wystawia tylko błędy langversion po zastosowaniu OverloadResolutionPriorityAttribute
, nie, gdy rzeczywiście ma wpływ na wszystko. Ta decyzja została podjęta, ponieważ istnieją interfejsy API, które BCL doda (zarówno teraz, jak i z czasem), które zaczną używać tego atrybutu. Jeśli użytkownik ręcznie ustawi wersję językową z powrotem na C# 12 lub wcześniejszą, może zobaczyć tych członków i, w zależności od naszego zachowania związanego z wersją językową, albo:
- Jeśli zignorujemy atrybut w języku C# <13, wystąpi błąd niejednoznaczności, ponieważ interfejs API jest naprawdę niejednoznaczny bez atrybutu lub;
- Jeśli występuje błąd, gdy atrybut wpływał na wynik, pojawi się problem z używaniem interfejsu API. Będzie to szczególnie złe, ponieważ
Debug.Assert(bool)
traci na znaczeniu w .NET 9. - Jeśli w trybie dyskretnym zmienimy rozdzielczość, napotkamy potencjalnie inne zachowanie między różnymi wersjami kompilatora, jeśli rozumie on atrybut, a inny nie.
Ostatnie zachowanie zostało wybrane, ponieważ zapewnia największą zgodność wsteczną, ale wynik tej zmiany może być zaskakujący dla niektórych użytkowników. Czy powinniśmy to potwierdzić lub wybrać jedną z pozostałych opcji?
Odpowiedź
Przejdziemy z opcją 1, dyskretnie ignorując atrybut w poprzednich wersjach językowych.
Alternatywy
Poprzednia propozycja próbowała określić podejście BinaryCompatOnlyAttribute
, które było bardzo rygorystyczne w usuwaniu rzeczy z pola widzenia. Jednak to ma wiele trudnych problemów z implementacją, które oznaczają, że propozycja jest zbyt silna, aby być przydatna (na przykład zapobieganie testowaniu starych interfejsów API) lub tak słabych, że brakowało niektórych pierwotnych celów (takich jak możliwość posiadania interfejsu API, który w przeciwnym razie byłby uważany za niejednoznaczne wywołanie nowego interfejsu API). Ta wersja jest replikowana poniżej.
BinaryCompatOnlyAttribute Proposal (przestarzałe)
BinaryCompatOnlyAttribute
Szczegółowy projekt
System.BinaryCompatOnlyAttribute
Wprowadzamy nowy atrybut zarezerwowany:
namespace System;
// Excludes Assembly, GenericParameter, Module, Parameter, ReturnValue
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Constructor
| AttributeTargets.Delegate
| AttributeTargets.Enum
| AttributeTargets.Event
| AttributeTargets.Field
| AttributeTargets.Interface
| AttributeTargets.Method
| AttributeTargets.Property
| AttributeTargets.Struct,
AllowMultiple = false,
Inherited = false)]
public class BinaryCompatOnlyAttribute : Attribute {}
Po zastosowaniu do członka typu, ten członek jest traktowany przez kompilator jako niedostępny we wszystkich miejscach, co oznacza, że nie bierze udziału w wyszukiwaniu członków, rozwiązywaniu przeciążeń ani żadnym innym podobnym procesie.
Domeny ułatwień dostępu
Aktualizujemy domeny ułatwień dostępu §7.5.3 w następujący sposób:
domena ułatwień dostępu elementu członkowskiego składa się z (prawdopodobnie rozłącznych) sekcji tekstu programu, w których jest dozwolony dostęp do elementu członkowskiego. Do celów definiowania domeny ułatwień dostępu elementu członkowskiego mówi się, że element członkowski jest najwyższego poziomu, jeśli nie jest zadeklarowany w obrębie typu, a element członkowski jest zagnieżdżony, jeśli jest zadeklarowany w innym typie. Ponadto tekst programu jest definiowany jako cały tekst zawarty we wszystkich jednostkach kompilacji programu, a tekst typu jest definiowany jako cały tekst zawarty w type_declarationtego typu (w tym, ewentualnie, typy zagnieżdżone wewnątrz tego typu).
Zakres dostępności wstępnie zdefiniowanego typu (na przykład
object
,int
lubdouble
) jest nieograniczony.Domena dostępności niezwiązanego typu najwyższego poziomu
T
(§8.4.4) zadeklarowana w programieP
jest zdefiniowana w następujący sposób:
- Jeśli
T
jest oznaczoneBinaryCompatOnlyAttribute
, domena dostępnościT
jest całkowicie niedostępna z tekstu programuP
i dowolnego programu, który odwołuje się doP
.- Jeśli zadeklarowana dostępność
T
jest publiczna, domeną ułatwień dostępuT
jest tekst programuP
i dowolny program, który odwołuje się doP
.- Jeśli zadeklarowana dostępność
T
jest wewnętrzna, domena dostępnościT
to tekst programuP
.Uwaga: Z tych definicji wynika, że domena ułatwień dostępu typu unbound najwyższego poziomu jest zawsze co najmniej tekstem programu, w którym ten typ jest zadeklarowany. uwaga końcowa
Domena dostępności dla skonstruowanego typu
T<A₁, ..., Aₑ>
jest przecięciem domeny dostępności niezwiązanego typu ogólnegoT
oraz domen dostępności argumentów typuA₁, ..., Aₑ
.Zakres dostępności zagnieżdżonego elementu członkowskiego
M
zadeklarowanego w typieT
w ramach programuP
jest zdefiniowany w następujący sposób (zauważając, żeM
może być sam w sobie typem):
- Jeśli
M
jest oznaczony jakoBinaryCompatOnlyAttribute
, domena dostępnościM
jest całkowicie niedostępna dla kodu programuP
i dla każdego programu, który odwołuje się doP
.- Jeśli zadeklarowana dostępność
M
jestpublic
, domena ułatwień dostępuM
jest domeną ułatwień dostępuT
.- Jeśli zadeklarowana dostępność
M
jestprotected internal
, niechD
będzie unią tekstu programuP
i tekstu programu dowolnego typu pochodzącego zT
, który zadeklarowano pozaP
. Domena ułatwień dostępuM
to skrzyżowanie domeny ułatwień dostępuT
zD
.- Jeśli zadeklarowana dostępność
M
jestprivate protected
, niechD
będzie przecięciem tekstu programuP
i tekstu programuT
oraz dowolnego typu pochodzącego zT
. Domena ułatwień dostępuM
to skrzyżowanie domeny ułatwień dostępuT
zD
.- Jeśli zadeklarowana dostępność
M
jestprotected
, niechD
będzie unią tekstu programuT
oraz tekstu programu dowolnego typu pochodnego odT
. Domena ułatwień dostępuM
to skrzyżowanie domeny ułatwień dostępuT
zD
.- Jeśli zadeklarowana dostępność
M
jestinternal
, domena ułatwień dostępuM
jest skrzyżowaniem domeny ułatwień dostępuT
z tekstem programuP
.- Jeśli zadeklarowana dostępność
M
jestprivate
, domena dostępnościM
to tekst programuT
.
Celem tych dodatków jest sprawienie, aby członkowie oznaczeni BinaryCompatOnlyAttribute
byli całkowicie niedostępni dla dowolnej lokalizacji, nie uczestniczyli w wyszukiwaniu członków i nie mieli wpływu na resztę programu. W związku z tym oznacza to, że nie mogą implementować elementów członkowskich interfejsu, nie mogą wywoływać siebie nawzajem i nie mogą być zastępowane (metody wirtualne), ukryte lub zaimplementowane (elementy członkowskie interfejsu). To, czy jest to zbyt ścisłe, jest przedmiotem kilku otwartych pytań poniżej.
Nierozwiązane pytania
Metody wirtualne i zastępowanie
Co robimy, gdy metoda wirtualna jest oznaczona jako BinaryCompatOnly
? Przesłonięcia w klasie pochodnej mogą nawet nie znajdować się w bieżącym zbiorze, i może się zdarzyć, że użytkownik chce wprowadzić nową wersję metody, która na przykład różni się tylko typem zwracanym, co w języku C# zwykle nie zezwala na przeciążenie. Co się stanie z wszelkimi przesłonięciami tej poprzedniej metody podczas ponownego kompilowania? Czy mogą zastąpić BinaryCompatOnly
element, jeśli są również oznaczone jako BinaryCompatOnly
?
Użyj w ramach tej samej biblioteki DLL
Ten wniosek stwierdza, że komponenty BinaryCompatOnly
nie są widoczne nigdzie, nawet w zestawieniu, które jest obecnie kompilowane. Czy to zbyt surowe, czy członkowie BinaryCompatAttribute
muszą się ze sobą połączyć?
Niejawne implementowanie elementów członkowskich interfejsu
Czy członkowie BinaryCompatOnly
mogą implementować członków interfejsu? A może powinno się im tego zabronić? Wymagałoby to, aby gdy użytkownik chce przekształcić niejawną implementację interfejsu w BinaryCompatOnly
, musiał dodatkowo zapewnić jawną implementację interfejsu. Prawdopodobnie obejmowałoby to sklonowanie tej samej treści co członek BinaryCompatOnly
, ponieważ jawna implementacja interfejsu nie będzie już w stanie zobaczyć oryginalnego członka.
Implementowanie elementów interfejsu oznaczonych BinaryCompatOnly
Co robimy, gdy element członkowski interfejsu został oznaczony jako BinaryCompatOnly
? Typ nadal musi zapewnić implementację dla tego członka; może być tak, że musimy po prostu powiedzieć, że członkowie interfejsu nie mogą być oznaczeni jako BinaryCompatOnly
.
C# feature specifications