Freigeben über


Priorität der Überladungsauflösung

Hinweis

Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede sind in den entsprechenden Anmerkungen zum Language Design Meeting (LDM) festgehalten.

Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Champion Issue: https://github.com/dotnet/csharplang/issues/7706

Zusammenfassung

Wir führen ein neues Attribut, System.Runtime.CompilerServices.OverloadResolutionPriority, ein, das von API-Autoren verwendet werden kann, um die relative Priorität von Überladungen innerhalb eines bestimmten Typs anzupassen. Dies dient dazu, API-Verbraucher dazu zu bewegen, spezifische APIs zu nutzen, auch wenn diese nach den Überladungsregeln von C# normalerweise als mehrdeutig angesehen oder nicht ausgewählt würden.

Motivation

API-Autoren stehen oft vor dem Problem, was sie mit einem Mitglied tun sollen, nachdem es veraltet ist. Aus Gründen der Abwärtskompatibilität behalten viele das vorhandene Mitglied dauerhaft bei, wobei ObsoleteAttribute auf Fehler festgelegt wird, um zu vermeiden, dass Verbraucher, die zur Laufzeit ein Upgrade der Binärdateien durchführen, davon betroffen sind. Dies betrifft vor allem Plugin-Systeme, bei denen der Autor eines Plugins keine Kontrolle über die Umgebung hat, in der das Plugin ausgeführt wird. Der Ersteller der Umgebung möchte vielleicht eine ältere Methode beibehalten, aber den Zugriff auf sie für neu entwickelten Code blockieren. Allerdings ist ObsoleteAttribute allein nicht ausreichend. Der Typ oder das Mitglied ist in der Überladungsauflösung immer noch sichtbar und kann zu unerwünschten Fehlern bei der Überladungsauflösung führen, wenn es zwar eine vollkommen gute Alternative gibt, diese aber entweder mit dem veralteten Mitglied mehrdeutig ist oder das Vorhandensein des veralteten Mitglieds dazu führt, dass die Überladungsauflösung vorzeitig beendet wird, ohne dass das gute Mitglied jemals berücksichtigt wird. Aus diesem Grund möchten wir den API-Autoren eine Möglichkeit an die Hand geben, die Überladungsauflösung bei der Auflösung der Mehrdeutigkeit anzuleiten, damit sie ihre API-Oberflächen weiterentwickeln und die Benutzer auf leistungsfähige APIs lenken können, ohne das Benutzererlebnis zu kompromittieren.

Das Base Class Libraries (BCL)-Team hat mehrere Beispiele, bei denen sich dies als nützlich erweisen kann. Einige (hypothetische) Beispiele sind:

  • Erstellen einer Überladung von Debug.Assert, die CallerArgumentExpression verwendet, um den Ausdruck zu erhalten, der behauptet wird, sodass er in die Nachricht aufgenommen werden kann, und ihn gegenüber der bestehenden Überladung bevorzugt macht.
  • So wird string.IndexOf(string, StringComparison = Ordinal) gegenüber string.IndexOf(string) bevorzugt. Dies müsste als potenzielle Änderung diskutiert werden, aber es gibt einige Stimmen, die meinen, dass dies die bessere Vorgabe ist und eher dem entspricht, was der Benutzer beabsichtigt.
  • Eine Kombination aus diesem Vorschlag und CallerAssemblyAttribute würde Methoden, die eine implizite Identität des Aufrufers haben, die Möglichkeit bieten, teure Stack-Walks zu vermeiden. Assembly.Load(AssemblyName) macht das heute schon, und es könnte viel effizienter sein.
  • Microsoft.Extensions.Primitives.StringValues stellt eine implizite Umwandlung in string und string[] dar. Das bedeutet, dass er mehrdeutig ist, wenn er an eine Methode mit params string[] und params ReadOnlySpan<string> Überladungen übergeben wird. Dieses Attribut kann verwendet werden, um eine der Überladungen zu priorisieren, um Mehrdeutigkeiten zu vermeiden.

Detailliertes Design

Priorität der Überladungsauflösung

Wir definieren ein neues Konzept, overload_resolution_priority, das während des Prozesses der Auflösung einer Methodengruppe verwendet wird. overload_resolution_priority ist ein 32-Bit-Ganzzahlwert. Alle Methoden haben standardmäßig eine overload_resolution_priority von 0, und dies kann durch Anwendung von OverloadResolutionPriorityAttribute auf eine Methode geändert werden. Wir aktualisieren Abschnitt §12.6.4.1 der C#-Spezifikation wie folgt (Änderung in fett):

Sobald die Kandidatenfunktionsmitglieder und die Argumentliste identifiziert sind, ist die Auswahl des besten Funktionsmitglieds in allen Fällen die gleiche:

  • Zunächst wird die Menge der in Frage kommenden Funktionsmitglieder auf die Funktionsmitglieder reduziert, die in Bezug auf die gegebene Argumentliste anwendbar sind (§ 12.6.4.2). Wenn dieses reduzierte Set leer ist, tritt ein Kompilierfehler auf.
  • Dann wird die reduzierte Menge der in Frage kommenden Mitglieder nach dem deklarierten Typ gruppiert. Innerhalb jeder Gruppe:
    • Die Mitglieder der Kandidatenfunktionen sind nach overload_resolution_priority geordnet. Wenn es sich bei dem Mitglied um eine Überladung handelt, stammt die overload_resolution_priority von der am wenigsten abgeleiteten Deklaration dieses Mitglieds.
    • Alle Mitglieder, die eine niedrigere overload_resolution_priority als die höchste in ihrer deklarierenden Typgruppe haben, werden entfernt.
  • Die reduzierten Gruppen werden dann zu der endgültigen Festlegung der in Frage kommenden Funktionsmitglieder rekombiniert.
  • Dann wird das beste Mitglied aus der Menge der in Frage kommenden Funktionsmitglieder festgelegt. Wenn das Set nur ein Funktionsmitglied enthält, dann ist dieses Funktionsmitglied das beste Funktionsmitglied. Andernfalls ist das beste Funktionsmitglied dasjenige, das in Bezug auf die gegebene Argumentliste besser ist als alle anderen Funktionsmitglieder, vorausgesetzt, dass jedes Funktionsmitglied mit allen anderen Funktionsmitgliedern anhand der Regeln in 12.6.4.3 verglichen wird. Wenn es nicht genau ein Funktionsmitglied gibt, das besser ist als alle anderen Funktionsmitglieder, dann ist der Aufruf des Funktionsmitglieds zweideutig und es kommt zu einem Bindungszeitfehler.

Diese Funktion würde beispielsweise dazu führen, dass der folgende Code-Schnipsel "Span" und nicht "Array" ausgibt:

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");
}

Die Auswirkung dieser Änderung ist, dass wir, wie beim Pruning für die meisten abgeleiteten Typen, ein letztes Pruning für die Priorität der Überladungsauflösung hinzufügen. Da dieses Pruning ganz am Ende der Überladungsauflösung stattfindet, bedeutet dies, dass ein Basistyp seinen Mitgliedern keine höhere Priorität einräumen kann als einem abgeleiteten Typ. Dies ist beabsichtigt und verhindert ein Wettrüsten, bei dem ein Basistyp versucht, immer besser als ein abgeleiteter Typ zu sein. Zum Beispiel:

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");
}

Negative Zahlen sind zugelassen und können verwendet werden, um eine bestimmte Überladung als schlechter als alle anderen Standardüberladungen zu kennzeichnen.

Die overload_resolution_priority eines Mitglieds stammt von der am wenigsten abgeleiteten Deklaration dieses Mitglieds. Die overload_resolution_priority wird nicht vererbt oder von Schnittstellenmitgliedern abgeleitet, die ein Mitglied eines Typs implementieren kann, und bei einem Mitglied Mx, das ein Schnittstellenmitglied Mi implementiert, wird keine Warnung ausgegeben, wenn Mx und Mi unterschiedliche overload_resolution_priorities haben.

NB: Mit dieser Regel soll das Verhalten des Modifikators params repliziert werden.

System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute

Wir führen das folgende Attribut in die BCL ein:

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;
}

Alle Methoden in C# haben standardmäßig eine overload_resolution_priority von 0, es sei denn, sie sind mit dem Attribut OverloadResolutionPriorityAttribute versehen. Wenn sie mit diesem Attribut versehen sind, dann ist ihre overload_resolution_priority der ganzzahlige Wert, der dem ersten Argument des Attributs übergeben wird.

Es ist ein Fehler, OverloadResolutionPriorityAttribute auf die folgenden Orte anzuwenden:

  • Indexer-fremde Eigenschaften
  • Eigenschaft, Indexer oder Ereignisaccessoren
  • Konvertierungsoperatoren
  • Lambdas
  • Local functions (Lokale Funktionen)
  • Finalizer
  • Statische Konstruktoren

Attribute, die an diesen Stellen in den Metadaten vorkommen, werden von C# ignoriert.

Es ist ein Fehler, OverloadResolutionPriorityAttribute an einem Speicherort anzuwenden, an dem es ignoriert werden würde, wie z. B. bei einer Überladung einer Basismethode, da die Priorität aus der am wenigsten abgeleiteten Deklaration eines Mitglieds gelesen wird.

NB: Dies weicht absichtlich vom Verhalten des params Modifikators ab, der die Möglichkeit bietet, die Priorität zu ändern oder hinzuzufügen, wenn er ignoriert wird.

Aufrufbarkeit von Mitgliedern

Ein wichtiger Vorbehalt für OverloadResolutionPriorityAttribute ist, dass er bestimmte Mitglieder von der Quelle aus effektiv nicht aufrufbar machen kann. Zum Beispiel:

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) {}
}

Bei diesen Beispielen werden die Standard-Prioritätsüberladungen effektiv unwirksam und sind nur durch ein paar Schritte aufrufbar, die etwas zusätzlichen Aufwand erfordern:

  • Konvertierung der Methode in einen Delegaten und anschließende Verwendung dieses Delegaten.
    • Bei einigen Referenztyp-Abweichungsszenarien, wie z. B. M3(object), das gegenüber M3(string) priorisiert ist, wird diese Strategie fehlschlagen.
    • Bedingte Methoden, wie z. B. M2, wären mit dieser Strategie ebenfalls nicht aufrufbar, da bedingte Methoden nicht in Delegaten umgewandelt werden können.
  • Verwenden Sie die UnsafeAccessor Runtime Funktion, um sie über die passende Signatur aufzurufen.
  • Manuelle Verwendung von Reflection, um einen Verweis auf die Methode abzurufen und sie dann aufzurufen.
  • Code, der nicht neu kompiliert wird, wird weiterhin die alten Methoden aufrufen.
  • Handgeschriebene IL kann angeben, was immer sie will.

Offene Fragen

Gruppierung von Erweiterungsmethoden (beantwortet)

Wie derzeit formuliert, werden Erweiterungsmethoden nur innerhalb ihres eigenen Typs nach Priorität geordnet. Zum Beispiel:

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 {}

Sollten wir bei der Überladungsauflösung für Mitglieder von Erweiterungen nicht nach dem deklarierten Typ sortieren und stattdessen alle Erweiterungen innerhalb desselben Bereichs berücksichtigen?

Antwort

Wir werden immer gruppieren. Das obige Beispiel wird Ext2 ReadOnlySpan ausgeben

Attributvererbung bei Überladungen (beantwortet)

Soll das Attribut vererbt werden? Wenn nicht, was ist die Priorität des überladenen Mitglieds?
Wenn das Attribut auf einem virtuellen Mitglied angegeben ist, sollte eine Überladung dieses Mitglieds erforderlich sein, um das Attribut zu wiederholen?

Antwort

Das Attribut wird nicht als vererbt markiert. Wir werden uns die am wenigsten abgeleitete Deklaration eines Mitglieds ansehen, um seine Priorität bei der Überladungsauflösung zu bestimmen.

Anwendungsfehler oder Warnung bei Überladung (beantwortet)

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?
}

Was sollten wir bei der Anwendung eines OverloadResolutionPriorityAttribute in einem Kontext tun, in dem es ignoriert wird, wie z. B. bei einer Überladung:

  1. Tun Sie nichts, ignorieren Sie es stillschweigend.
  2. Geben Sie eine Warnung aus, dass das Attribut ignoriert werden soll.
  3. Eine Fehlermeldung ausgeben, dass das Attribut nicht zugelassen ist.

3 ist der vorsichtigste Ansatz, wenn wir denken, dass es in der Zukunft eine Stelle geben könnte, an der wir eine Überladung zulassen möchten, um dieses Attribut festzulegen.

Antwort

Wir werden mit 3 weitermachen und die Anwendung an Orten blockieren, an denen sie ignoriert werden würde.

Implizite Implementierung von Schnittstellen (beantwortet)

Wie sollte sich eine implizite Implementierung einer Schnittstelle verhalten? Sollte es erforderlich sein, OverloadResolutionPriority anzugeben? Wie soll sich der Compiler verhalten, wenn er auf eine implizite Implementierung ohne Priorität stößt? Dies wird höchstwahrscheinlich passieren, da eine Schnittstellenbibliothek aktualisiert werden kann, aber eine Implementierung nicht. Der Stand der Technik bei params ist, den Wert nicht anzugeben und nicht zu übernehmen:

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"); }
}

Unsere Optionen sind:

  1. Befolgen Sie params. OverloadResolutionPriorityAttribute wird nicht implizit übernommen oder muss nicht angegeben werden.
  2. Übernehmen Sie das Attribut implizit.
  3. Übernehmen Sie das Attribut nicht implizit, sondern lassen Sie es auf der Site angeben.
    1. Daraus ergibt sich eine zusätzliche Frage: Wie sollte sich der Compiler verhalten, wenn er auf dieses Szenario mit kompilierten Referenzen stößt?

Antwort

Wir entscheiden uns für 1.

Weitere Anwendungsfehler (Beantwortet)

Es gibt noch ein paar weitere Ort wie dieser, die bestätigt werden müssen. Dazu gehören:

  • Konvertierungsoperatoren - In der Spezifikation steht nicht, dass Konvertierungsoperatoren eine Überladungsauflösung durchlaufen, sodass die Implementierung die Anwendung auf diese Mitglieder blockiert. Sollte das bestätigt werden?
  • Lambdas - In ähnlicher Weise unterliegen Lambdas nie der Überladungsauflösung, sodass die Implementierung sie blockiert. Sollte das bestätigt werden?
  • Destruktoren - auch diese sind derzeit blockiert.
  • Statische Konstruktoren - auch diese sind derzeit blockiert.
  • Lokale Funktionen - Diese sind derzeit nicht blockiert, da sie einer Überladungsauflösung unterliegen, Sie können sie nur nicht überladen. Dies ist vergleichbar damit, dass wir keinen Fehler machen, wenn das Attribut auf ein Element eines Typs angewendet wird, der nicht überladen ist. Soll dieses Verhalten bestätigt werden?

Antwort

Alle oben aufgeführten Standorte sind blockiert.

Langversion-Verhalten (beantwortet)

Die Implementierung gibt derzeit nur Langversion-Fehler aus, wenn OverloadResolutionPriorityAttribute angewendet wird, nicht, wenn sie tatsächlich etwas beeinflusst. Diese Entscheidung wurde getroffen, weil es APIs gibt, die die BCL hinzufügen wird (sowohl jetzt als auch im Laufe der Zeit), die dieses Attribut verwenden werden. Wenn der Benutzer seine Sprachversion manuell auf C# 12 oder früher festlegt, kann er diese Mitglieder sehen und, je nach unserem Langversion-Verhalten, entweder:

  • Wenn wir das Attribut in C# <13 ignorieren, wird ein Mehrdeutigkeitsfehler auftreten, weil die API ohne das Attribut wirklich mehrdeutig ist, oder;
  • Wenn wir einen Fehler auslösen, wenn das Attribut das Ergebnis beeinflusst, führen wir einen Fehler aus, dass die API nicht verwendbar ist. Das ist besonders schlimm, weil Debug.Assert(bool) in .NET 9 nicht mehr priorisiert wird, oder;
  • Wenn wir die Auflösung stillschweigend ändern, kann es zu einem unterschiedlichen Verhalten zwischen verschiedenen Compiler-Versionen kommen, wenn eine das Attribut versteht und eine andere nicht.

Das letzte Verhalten wurde gewählt, weil es die meiste Vorwärtskompatibilität ergibt, aber das veränderte Ergebnis könnte für einige Benutzende überraschend sein. Sollen wir dies bestätigen oder eine der anderen Optionen wählen?

Antwort

Wir werden mit Option 1 fortfahren und das Attribut in früheren Sprachversionen stillschweigend ignorieren.

Alternativen

Ein früherer Vorschlag hat versucht, einen BinaryCompatOnlyAttributeAnsatz zu spezifizieren, der die Sichtbarkeit von Dingen sehr stark einschränkte. Das hat jedoch eine Menge praktischer Implementierungsprobleme zur Folge, die entweder bedeuten, dass der Vorschlag zu strikt ist, um nützlich zu sein (z. B. verhindert er das Testen alter APIs), oder er ist so unzureichend, dass er einige der ursprünglichen Ziele verfehlt (wie z. B. die Möglichkeit, eine API, die sonst als mehrdeutig gelten würde, als neue API zu betrachten). Diese Version wird im Folgenden repliziert.

BinäryCompatOnlyAttribute-Vorschlag (veraltet)

BinaryCompatOnlyAttribute

Detailliertes Design

System.BinaryCompatOnlyAttribute

Wir führen ein neues reserviertes Attribut ein:

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 {}

Wenn es auf ein Mitglied eines Typs angewandt wird, wird dieses Mitglied vom Compiler an jedem Ort als unzugänglich behandelt, d. h. es trägt nicht zur Mitgliedersuche, Überladungsauflösung oder einem anderen ähnlichen Prozess bei.

Zugriffsdomänen

Wir aktualisieren §7.5.3 Zugriffsdomänen wie folgend:

Die Zugriffsdomäne eines Mitglieds besteht aus den (möglicherweise disjunkten) Abschnitten des Programmtexts, in denen der Zugriff auf das Mitglied erlaubt ist. Für die Zwecke der Definition der Zugriffsdomäne eines Mitglieds wird ein Mitglied als Top-Level bezeichnet, wenn es nicht innerhalb eines Typs deklariert ist, und ein Mitglied wird als eingebettet bezeichnet, wenn es innerhalb eines anderen Typs deklariert ist. Darüber hinaus ist der Programmtext eines Programms definiert als der gesamte Text, der in allen Kompilierungseinheiten des Programms enthalten ist, und der Programmtext eines Typs ist definiert als der gesamte Text, der in den Typdeklarationen dieses Typs enthalten ist (einschließlich möglicherweise innerhalb des Typs eingebetteter Typen).

Die Zugriffsdomäne eines vordefinierten Typs (wie z. B. object, int, oder double) ist unbegrenzt.

Die Zugriffsdomäne eines ungebundenen Typs der obersten Ebene T (§8.4.4), der in einem Programm P deklariert ist, ist wie folgt definiert:

  • Wenn T mit BinaryCompatOnlyAttributegekennzeichnet ist, ist der Zugänglichkeitsbereich von T für den Programmtext von P und alle Programme, die auf Pverweisen, völlig unzugänglich.
  • Wenn die deklarierte Zugriffsfreiheit von T öffentlich ist, ist die Zugriffsdomäne von T der Programmtext von P und jedes Programm, das auf P verweist.
  • Wenn die deklarierte Zugriffsfreiheit von T intern ist, ist die Zugriffsdomäne von T der Programmtext von P.

Anmerkung: Aus diesen Definitionen folgt, dass die Zugriffsdomäne eines ungebundenen Typs der obersten Ebene immer mindestens der Programmtext des Programms ist, in dem dieser Typ deklariert ist. Hinweisende

Die Zugänglichkeitsbereiche für einen konstruierten Typ T<A₁, ..., Aₑ> sind die Schnittmenge der Zugänglichkeitsbereiche des ungebundenen generischen Typs T und der Zugänglichkeitsbereiche der Typargumente A₁, ..., Aₑ.

Die Zugriffsdomäne eines eingebetteten Mitglieds M, das in einem Typ T innerhalb eines Programms P deklariert ist, ist wie folgt definiert (wobei zu beachten ist, dass M selbst möglicherweise ein Typ ist):

  • Wenn M mit BinaryCompatOnlyAttributegekennzeichnet ist, ist der Zugänglichkeitsbereich von M für den Programmtext von P und alle Programme, die auf Pverweisen, völlig unzugänglich.
  • Wenn die deklarierte Zugriffsart von M den Wert public hat, entspricht die Zugriffsdomäne von M der von T.
  • Wenn die deklarierte Zugriffsmöglichkeit von Mprotected internal ist, sei D die Einheit des Programmtextes von P und des Programmtextes eines beliebigen von T abgeleiteten Typs, der außerhalb von P deklariert ist. Der Zugänglichkeitsbereich von M ist die Schnittmenge des Zugänglichkeitsbereichs von T mit D.
  • Wenn die deklarierte Zugriffsmöglichkeit von Mprivate protected ist, sei D die Schnittmenge aus dem Programmtext von P und dem Programmtext von T und einem beliebigen von T abgeleiteten Typ. Der Zugänglichkeitsbereich von M ist die Schnittmenge des Zugänglichkeitsbereichs von T mit D.
  • Wenn die deklarierte Zugriffsmöglichkeit von Mprotected ist, sei D die Einheit aus dem Programmtext von Tund dem Programmtext eines beliebigen Typs, der von T abgeleitet ist. Der Zugänglichkeitsbereich von M ist die Schnittmenge des Zugänglichkeitsbereichs von T mit D.
  • Wenn die deklarierte Zugriffsart von M den Wert internal hat, entspricht die Zugriffsdomäne von M der Schnittmenge zwischen der Zugriffsdomäne von T und dem Programmtext von P.
  • Wenn die deklarierte Zugriffsart von M den Wert private hat, entspricht die Zugriffsdomäne von M dem Programmtext von T.

Das Ziel dieser Ergänzungen ist es, dass Mitglieder, die mit BinaryCompatOnlyAttribute markiert sind, an keinem Ort zugänglich sind, nicht an der Mitgliedersuche teilnehmen und den Rest des Programms nicht beeinflussen können. Das bedeutet, dass sie keine Schnittstellenmitglieder implementieren können, dass sie sich nicht gegenseitig aufrufen können und dass sie nicht überschrieben (virtuelle Methoden), versteckt oder implementiert werden können (Schnittstellenmitglieder). Ob dies zu streng ist, ist Gegenstand mehrerer offener Fragen weiter unten.

Ungelöste Fragen

Virtuelle Methoden und Überschreibungen

Was tun wir, wenn eine virtuelle Methode als BinaryCompatOnly gekennzeichnet ist? Überschreibungen in einer abgeleiteten Klasse befinden sich möglicherweise nicht einmal in der aktuellen Assembly. Es könnte sein, dass der Benutzer eine neue Version einer Methode einführen möchte, die sich z. B. nur durch den Rückgabetyp unterscheidet, was in C# normalerweise keine Überladung zulässt. Was passiert mit den Überschreibungen der vorherigen Methode beim Neukompilieren? Ist es ihnen erlaubt, das BinaryCompatOnly-Mitglied zu überschreiben, wenn sie auch als BinaryCompatOnly markiert sind?

Verwendung innerhalb der gleichen DLL

Dieser Vorschlag besagt, dass BinaryCompatOnly-Mitglieder nirgendwo sichtbar sind, nicht einmal in der Assembly, die gerade kompiliert wird. Ist das zu strikt, oder müssen BinaryCompatAttribute-Mitglieder möglicherweise miteinander verkettet werden?

Implizite Implementierung von Schnittstellenmitgliedern

Sollten BinaryCompatOnly-Mitglieder die Möglichkeit haben, Schnittstellenmitglieder zu implementieren? Oder sollten sie daran gehindert werden? Das würde bedeuten, dass ein Benutzer, der eine implizite Implementierung einer Schnittstelle in BinaryCompatOnly umwandeln möchte, zusätzlich eine explizite Implementierung der Schnittstelle bereitstellen müsste, die wahrscheinlich denselben Body wie das BinaryCompatOnly-Mitglied klont, da die explizite Implementierung der Schnittstelle das ursprüngliche Mitglied nicht mehr sehen könnte.

Implementierung von Schnittstellenmitgliedern, die mit BinaryCompatOnly markiert sind

Was tun wir, wenn ein Mitglied der Schnittstelle als BinaryCompatOnly markiert wurde? Der Typ muss immer noch eine Implementierung für dieses Mitglied bereitstellen. Es kann sein, dass wir einfach sagen müssen, dass Schnittstellenmitglieder nicht als BinaryCompatOnly markiert werden können.