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
, dieCallerArgumentExpression
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überstring.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 instring
undstring[]
dar. Das bedeutet, dass er mehrdeutig ist, wenn er an eine Methode mitparams string[]
undparams 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überM3(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.
- Bei einigen Referenztyp-Abweichungsszenarien, wie z. B.
- 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:
- Tun Sie nichts, ignorieren Sie es stillschweigend.
- Geben Sie eine Warnung aus, dass das Attribut ignoriert werden soll.
- 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:
- Befolgen Sie
params
.OverloadResolutionPriorityAttribute
wird nicht implizit übernommen oder muss nicht angegeben werden. - Übernehmen Sie das Attribut implizit.
- Übernehmen Sie das Attribut nicht implizit, sondern lassen Sie es auf der Site angeben.
- 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 BinaryCompatOnlyAttribute
Ansatz 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
, oderdouble
) ist unbegrenzt.Die Zugriffsdomäne eines ungebundenen Typs der obersten Ebene
T
(§8.4.4), der in einem ProgrammP
deklariert ist, ist wie folgt definiert:
- Wenn
T
mitBinaryCompatOnlyAttribute
gekennzeichnet ist, ist der Zugänglichkeitsbereich vonT
für den Programmtext vonP
und alle Programme, die aufP
verweisen, völlig unzugänglich.- Wenn die deklarierte Zugriffsfreiheit von
T
öffentlich ist, ist die Zugriffsdomäne vonT
der Programmtext vonP
und jedes Programm, das aufP
verweist.- Wenn die deklarierte Zugriffsfreiheit von
T
intern ist, ist die Zugriffsdomäne vonT
der Programmtext vonP
.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 TypsT
und der Zugänglichkeitsbereiche der TypargumenteA₁, ..., Aₑ
.Die Zugriffsdomäne eines eingebetteten Mitglieds
M
, das in einem TypT
innerhalb eines ProgrammsP
deklariert ist, ist wie folgt definiert (wobei zu beachten ist, dassM
selbst möglicherweise ein Typ ist):
- Wenn
M
mitBinaryCompatOnlyAttribute
gekennzeichnet ist, ist der Zugänglichkeitsbereich vonM
für den Programmtext vonP
und alle Programme, die aufP
verweisen, völlig unzugänglich.- Wenn die deklarierte Zugriffsart von
M
den Wertpublic
hat, entspricht die Zugriffsdomäne vonM
der vonT
.- Wenn die deklarierte Zugriffsmöglichkeit von
M
protected internal
ist, seiD
die Einheit des Programmtextes vonP
und des Programmtextes eines beliebigen vonT
abgeleiteten Typs, der außerhalb vonP
deklariert ist. Der Zugänglichkeitsbereich vonM
ist die Schnittmenge des Zugänglichkeitsbereichs vonT
mitD
.- Wenn die deklarierte Zugriffsmöglichkeit von
M
private protected
ist, seiD
die Schnittmenge aus dem Programmtext vonP
und dem Programmtext vonT
und einem beliebigen vonT
abgeleiteten Typ. Der Zugänglichkeitsbereich vonM
ist die Schnittmenge des Zugänglichkeitsbereichs vonT
mitD
.- Wenn die deklarierte Zugriffsmöglichkeit von
M
protected
ist, seiD
die Einheit aus dem Programmtext vonT
und dem Programmtext eines beliebigen Typs, der vonT
abgeleitet ist. Der Zugänglichkeitsbereich vonM
ist die Schnittmenge des Zugänglichkeitsbereichs vonT
mitD
.- Wenn die deklarierte Zugriffsart von
M
den Wertinternal
hat, entspricht die Zugriffsdomäne vonM
der Schnittmenge zwischen der Zugriffsdomäne vonT
und dem Programmtext vonP
.- Wenn die deklarierte Zugriffsart von
M
den Wertprivate
hat, entspricht die Zugriffsdomäne vonM
dem Programmtext vonT
.
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.
C# feature specifications