Udostępnij za pośrednictwem


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żywa CallerArgumentExpression 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) nad string.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ę na string i string[]. Oznacza to, że przekazany element jest niejednoznaczny w przypadku zastosowania metody z przeciążeniami params string[] i params 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 Miinterfejsu, 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 nad M3(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.
  • 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:

  1. Nic nie rób, niech dyskretnie zostanie zignorowany.
  2. Wydaj ostrzeżenie, że atrybut zostanie zignorowany.
  3. 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:

  1. Postępuj zgodnie z params. OverloadResolutionPriorityAttribute nie będzie przenoszona domyślnie ani wymagana do określenia.
  2. Przenieś atrybut niejawnie.
  3. Nie przenoś atrybutu niejawnie, należy go określić w miejscu wywołania.
    1. 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, intlub double) jest nieograniczony.

Domena dostępności niezwiązanego typu najwyższego poziomu T (§8.4.4) zadeklarowana w programie P jest zdefiniowana w następujący sposób:

  • Jeśli T jest oznaczone BinaryCompatOnlyAttribute, domena dostępności T jest całkowicie niedostępna z tekstu programu P i dowolnego programu, który odwołuje się do P.
  • Jeśli zadeklarowana dostępność T jest publiczna, domeną ułatwień dostępu T jest tekst programu P i dowolny program, który odwołuje się do P.
  • Jeśli zadeklarowana dostępność T jest wewnętrzna, domena dostępności T to tekst programu P.

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ólnego T oraz domen dostępności argumentów typu A₁, ..., Aₑ.

Zakres dostępności zagnieżdżonego elementu członkowskiego M zadeklarowanego w typie T w ramach programu Pjest zdefiniowany w następujący sposób (zauważając, że M może być sam w sobie typem):

  • Jeśli M jest oznaczony jako BinaryCompatOnlyAttribute, domena dostępności M jest całkowicie niedostępna dla kodu programu P i dla każdego programu, który odwołuje się do P.
  • Jeśli zadeklarowana dostępność M jest public, domena ułatwień dostępu M jest domeną ułatwień dostępu T.
  • Jeśli zadeklarowana dostępność M jest protected internal, niech D będzie unią tekstu programu P i tekstu programu dowolnego typu pochodzącego z T, który zadeklarowano poza P. Domena ułatwień dostępu M to skrzyżowanie domeny ułatwień dostępu T z D.
  • Jeśli zadeklarowana dostępność M jest private protected, niech D będzie przecięciem tekstu programu P i tekstu programu T oraz dowolnego typu pochodzącego z T. Domena ułatwień dostępu M to skrzyżowanie domeny ułatwień dostępu T z D.
  • Jeśli zadeklarowana dostępność M jest protected, niech D będzie unią tekstu programu Toraz tekstu programu dowolnego typu pochodnego od T. Domena ułatwień dostępu M to skrzyżowanie domeny ułatwień dostępu T z D.
  • Jeśli zadeklarowana dostępność M jest internal, domena ułatwień dostępu M jest skrzyżowaniem domeny ułatwień dostępu T z tekstem programu P.
  • Jeśli zadeklarowana dostępność M jest private, domena dostępności M to tekst programu T.

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.