Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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 werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
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/49
Zusammenfassung
Unterstützung kovarianter Rückgabetypen. Erlauben Sie insbesondere die Überschreibung einer Methode, um einen stärker abgeleiteten Rückgabetyp zu deklarieren als die Methode, die sie überschreibt, und ebenso die Überschreibung einer schreibgeschützten Eigenschaft, um einen stärker abgeleiteten Typ zu deklarieren. Deklarationen, die in abgeleiteten Typen überschrieben werden, müssen einen Rückgabetyp angeben, der mindestens so spezifisch ist wie der in den Basistypen angegebenen Überschreibungen. Aufrufer der Methode oder Eigenschaft erhalten auf statische Weise den verfeinerten Rückgabetyp bei einem Aufruf.
Motivation
Es ist ein gängiges Muster im Code, dass verschiedene Methodennamen erfunden werden müssen, um die Spracheinschränkung zu umgehen, dass Überschreibungen denselben Typ wie die überschriebene Methode zurückgeben müssen.
Dies wäre im Factory-Pattern nützlich. Beispiel: In der Roslyn-Codebasis hätten wir
class Compilation ...
{
public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
public override CSharpCompilation WithOptions(Options options)...
}
Detailliertes Design
Dies ist eine Spezifikation für kovariante Rückgabetypen in C#. Unsere Absicht ist es, die Überschreibung einer Methode zu erlauben, um einen stärker abgeleiteten Rückgabetyp zurückzugeben als die Methode, die sie überschreibt, und ebenso die Überschreibung einer schreibgeschützten Eigenschaft zu erlauben, um einen stärker abgeleiteten Rückgabetyp zurückzugeben. Aufrufer der Methode oder Eigenschaft erhalten den verfeinerten Rückgabetyp direkt aus einem Aufruf. Überschreibungen in abgeleiteten Typen müssen einen Rückgabetyp bereitstellen, der mindestens so spezifisch ist wie der in den Überschreibungen ihrer Basistypen angegebene.
Überschreiben der Klassenmethode
Die bestehende Einschränkung der Überschreibung von Klassen (§15.6.5)
- Die Überschreibungsmethode und die überschriebene Basismethode weisen den gleichen Rückgabetyp auf.
wird geändert in
- Die Überschreibungsmethode muss einen Rückgabetyp haben, der durch eine Identitätskonvertierung konvertierbar ist oder (wenn die Methode einen Wert zurückgibt, nicht eine Ref-Rückgabe), siehe §13.1.0.5 als implizite Verweiskonvertierung in den Rückgabetyp der überschriebenen Basismethode.
Und die folgenden zusätzlichen Anforderungen werden an die folgende Liste angefügt:
- Die Überschreibungsmethode muss einen Rückgabetyp haben, der durch eine Identitätskonvertierung umgewandelt werden kann oder (falls die Methode einen Wert zurückgibt – keine Ref-Rückgabe, §13.1.0.5) eine implizite Verweiskonvertierung in den Rückgabetyp jeder Überschreibung der überschriebenen Basismethode, die in einem (direkten oder indirekten) Basistyp der Überschreibungsmethode deklariert ist, ermöglicht.
- Der Rückgabetyp der Überschreibungsmethode muss mindestens genauso zugänglich sein wie die Überschreibungsmethode (Zugänglichkeitsbereiche – §7.5.3).
Diese Einschränkung erlaubt es, dass eine Überschreibungsmethode in einer private Klasse einen private Rückgabetyp haben kann. Es ist jedoch eine public Überschreibungsmethode in einem public Typ erforderlich, damit ein public Rückgabetyp vorhanden ist.
Klasseneigenschaft und Indexer-Überschreibung
Die bestehende Einschränkung der Überschreibung von Klassen (§15.7.6) Eigenschaften
Eine überschreibende Eigenschaftsdeklaration muss genau die gleichen Zugriffsmodifizierer und denselben Namen wie die geerbte Eigenschaft angeben, und es muss eine Identitätskonvertierung
zwischen dem Typ der überschreibenden und der geerbten Eigenschaftbestehen. Wenn die geerbte Eigenschaft nur über einen einzelnen Accessor verfügt (d. h., wenn die geerbte Eigenschaft entweder schreibgeschützt oder nur schreibend ist), darf die überschriebene Eigenschaft nur diesen Accessor enthalten. Wenn die geerbte Eigenschaft beide Accessoren enthält (d. h., wenn die geerbte Eigenschaft Lese- und Schreibzugriff hat), kann die überschreibende Eigenschaft entweder einen einzelnen Accessor oder beide Accessoren enthalten.
wird geändert in
Eine überschreibende Eigenschaftsdeklaration muss genau dieselben Zugriffsmodifizierer und denselben Namen wie die geerbte Eigenschaft aufweisen, und es muss eine Identitätskonvertierung oder (wenn die geerbte Eigenschaft schreibgeschützt ist und einen Wert zurückgibt – kein Ref-Rückgabe§13.1.0.5) eine implizite Verweiskonvertierung vom Typ der überschreibenden Eigenschaft in den Typ der geerbten Eigenschaft geben. Wenn die geerbte Eigenschaft nur über einen einzelnen Accessor verfügt (d. h., wenn die geerbte Eigenschaft entweder schreibgeschützt oder nur schreibend ist), darf die überschriebene Eigenschaft nur diesen Accessor enthalten. Wenn die geerbte Eigenschaft beide Accessoren enthält (d. h., wenn die geerbte Eigenschaft Lese- und Schreibzugriff hat), kann die überschreibende Eigenschaft entweder einen einzelnen Accessor oder beide Accessoren enthalten. Der Typ der überschreibenden Eigenschaft muss mindestens so zugänglich sein wie die überschreibende Eigenschaft (Zugänglichkeitsbereiche – §7.5.3).
Der Rest der unten aufgeführten Entwurfsspezifikation schlägt eine zusätzliche Erweiterung der kovarianten Rückgaben der Schnittstellenmethoden vor, auf die wir später eingehen werden.
Interface-Methode, -Eigenschaft und -Indexer-Überschreibung
Durch das Hinzufügen der DIM-Funktion in C# 8.0 zu den in einer Schnittstelle zulässigen Member-Typen unterstützen wir auch override-Member zusammen mit kovarianten Rückgaben. Diese Regeln folgen den Regeln der override-Member, wie für Klassen festgelegt, mit den folgenden Unterschieden:
Der folgende Text in den Klassen:
Die Methode, die durch eine Überschreibungsdeklaration überschrieben wird, wird als die überschriebene Basismethode bezeichnet. Für eine in einer Klasse
Mdeklarierten ÜberschreibungsmethodeCwird die überschriebene Basismethode bestimmt, indem jede Basisklasse vonCuntersucht wird. Dabei beginnt man mit der direkten BasisklasseCund setzt die Untersuchung mit jeder darauffolgenden direkten Basisklasse fort, bis in einem bestimmten Basisklassentyp mindestens eine zugängliche Methode gefunden wird, die nach Ersetzung der Typargumente dieselbe Signatur wieMaufweist.
erhält die entsprechende Spezifikation für Schnittstellen:
Die Methode, die durch eine Überschreibungsdeklaration überschrieben wird, wird als die überschriebene Basismethode bezeichnet. Bei einer in einer Schnittstelle
Mdeklarierten ÜberschreibungsmethodeIwird die überschreibende Basismethode bestimmt, indem jede direkte oder indirekte Basisschnittstelle vonIuntersucht wird und dabei die Menge der Schnittstellen erfasst wird, die eine zugängliche Methode deklarieren, die nach Substitution der Typargumente dieselbe Signatur wieMaufweist. Wenn dieser Satz von Schnittstellen über einen abgeleiteten Typ verfügt, auf den eine Identitäts- oder implizite Verweiskonvertierung von jedem Typ in diesem Satz vorhanden ist und dieser Typ eine eindeutige solche Methodendeklaration enthält, dann ist dies die überschriebene Basismethode.
Ebenso erlauben wir override-Eigenschaften und Indexer in Schnittstellen, wie für Klassen in §15.7.6 virtuelle, versiegelte, überschreibende und abstrakte Zugriffsmodifikatoren angegeben.
Namenssuche
Die Namenssuche bei Klassen-override-Deklarationen ändert derzeit das Ergebnis der Namenssuche, indem sie die gefundenen Member-Details aus der am stärksten abgeleiteten override-Deklaration in der Klassenhierarchie startend vom Typ des Qualifizierers des Bezeichners (oder this, wenn kein Qualifizierer vorhanden ist) aufzwingt. Zum Beispiel haben wir in §12.6.2.2 Entsprechende Parameter
Bei virtuellen Methoden und Indexern, die in Klassen definiert sind, wird die Parameterliste aus der ersten Deklaration oder Überschreibung des Funktions-Members ausgewählt, die ausgehend vom statischen Typ des Empfängers und beim Durchsuchen der Basisklassen gefunden wird.
zu diesem fügen wir hinzu
Bei virtuellen Methoden und Indexern, die in Schnittstellen definiert sind, wird die Parameterliste aus der Deklaration oder der Überschreibung des Funktions-Members ausgewählt, das im am meisten abgeleiteten Typ unter denen enthalten ist, die die Deklaration oder die Überschreibung des Funktions-Members enthalten. Es handelt sich um einen Kompilierzeitfehler, wenn ein solcher eindeutiger Typ nicht vorhanden ist.
Für den Ergebnistyp einer Eigenschaft oder eines Indexerzugriffs den vorhandenen Text
- Wenn
Ieine Instanzeigenschaft identifiziert, dann ist das Ergebnis ein Eigenschaftszugriff mit einem verbundenen Instanzausdruck vonEund einem verbundenen Typ, der dem Typ der Eigenschaft entspricht. WennTein Klassentyp ist, wird der zugeordnete Typ aus der ersten Deklaration oder Überschreibung der Eigenschaft gewählt, die gefunden wird, indem beiTbegonnen und die Basisklassen durchsucht werden.
wird erweitert mit
Wenn
Tein Schnittstellentyp ist, wird der zugeordnete Typ aus der Deklaration oder Überschreibung der Eigenschaft ausgewählt, die in dem am meisten abgeleitetenToder seinen direkten oder indirekten Basisschnittstellen gefunden wurde. Es handelt sich um einen Kompilierzeitfehler, wenn ein solcher eindeutiger Typ nicht vorhanden ist.
Es sollte eine ähnliche Änderung in §12.8.12.3 Indexer-Zugriff vorgenommen werden.
In §12.8.10 Aufrufausdrücke erweitern wir den vorhandenen Text
- Andernfalls ist das Ergebnis ein Wert mit einem zugeordneten Typ des Rückgabetyps der Methode oder des Delegaten. Wenn es sich bei dem Aufruf um eine Instanzmethode handelt und der Empfänger vom Klassentyp
Tist, wird der zugeordnete Typ aus der ersten Deklaration oder Überschreibung der Methode ausgewählt, die beginnend mitTgefunden wird, während die Basisklassen durchsucht werden.
durch
Wenn es sich um den Aufruf einer Instanzmethode handelt und der Empfänger vom Schnittstellentyp
Tist, wird der zugehörige Typ aus der Deklaration oder Überschreibung der gefundenen Methode ausgewählt, die in der am stärksten abgeleiteten Schnittstelle ausTund ihren direkten und indirekten Basisschnittstellen gefunden wird. Es handelt sich um einen Kompilierzeitfehler, wenn ein solcher eindeutiger Typ nicht vorhanden ist.
Implizite Schnittstellenimplementierungen
Dieser Abschnitt der Spezifikation
Für die Zwecke der Schnittstellenzuordnung entspricht ein Klassenmitglied
Aeinem SchnittstellenmitgliedB, wenn:
AundBsind Methoden, und die Namenslisten, Typlisten und die formellen Parameterlisten vonAundBsind identisch.AundBsind Eigenschaften, der Name und der Typ vonAundBsind identisch, undAhat die gleichen Accessoren wieB(Adarf zusätzliche Accessoren haben, wenn es sich nicht um eine explizite Schnittstellenmitgliedimplementierung handelt).AundBsind Ereignisse, und der Name und Typ vonAundBsind identisch.AundBsind Indexer, die Typ- und formale Parameterlisten vonAundBsind identisch, undAverfügt über dieselben Zugriffsmethoden wieB(Adarf zusätzliche Zugriffsmethoden haben, wenn es sich nicht um eine explizite Schnittstellenmitgliedimplementierung handelt).
wird wie folgt geändert:
Für die Zwecke der Schnittstellenzuordnung entspricht ein Klassenmitglied
Aeinem SchnittstellenmitgliedB, wenn:
AundBsind Methoden, und die Namen- und formalen Parameterlisten vonAundBsind identisch, und der Rückgabetyp vonAwird über eine Identität der impliziten Verweiskonvertierung in den RückgabetypBin den Rückgabetyp vonBkonvertiert.AundBsind Eigenschaften, der Name vonAundBist identisch,Ahat die gleichen Accessoren wieB(Adarf zusätzliche Accessoren haben, wenn es sich nicht um eine explizite Schnittstellen-Member-Implementierung handelt), und der Typ vonAwird über eine Identitätskonvertierung in den Rückgabetyp vonBkonvertiert oder, wennAeine schreibgeschützte Eigenschaft ist, eine implizite Verweiskonvertierung.AundBsind Ereignisse, und der Name und Typ vonAundBsind identisch.AundBsind Indexer, die formelle Parameterliste vonAundBist identisch,Ahat die gleichen Accessoren wieB(Adarf zusätzliche Accessoren haben, wenn es sich nicht um eine explizite Schnittstellen-Member-Implementierung handelt), und der Typ vonAwird über eine Identitätskonvertierung in den Rückgabetyp vonBkonvertiert oder, wennAein schreibgeschützter Indexer ist, eine implizite Verweiskonvertierung.
Dies ist technisch eine einschneidende Änderung, da das folgende Programm heute „C1.M” druckt, aber unter der vorgeschlagenen Überarbeitung „C2.M” drucken würde.
using System;
interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }
class Program
{
static void Main()
{
I1 i = new C2();
Console.WriteLine(i.M());
}
}
Aufgrund dieser bahnbrechenden Änderung sollten wir für implizite Implementierungen vielleicht keine kovarianten Rückgabetypen unterstützen.
Einschränkungen bei Schnittstellenimplementierungen
Wir benötigen eine Regel, dass eine explizite Schnittstellenimplementierung einen Rückgabetyp deklarieren muss, der mindestens genauso abgeleitet ist wie der Rückgabetyp, der in einer Überschreibung in deren Basisschnittstellen deklariert wurde.
Auswirkungen auf die API-Kompatibilität
TBD
Offene Probleme
Die Spezifikation sagt nicht, wie der Aufrufer den verfeinerten Rückgabetyp erhält. Vermutlich würde dies so erfolgen, wie Aufrufer die Parameterspezifikationen der meist abgeleiteten Überschreibung erhalten.
Wenn wir über die folgenden Schnittstellen verfügen:
interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); }
Beachten Sie, dass die Methoden I3 und I1.M() in I2.M() „zusammengeführt“ wurden. Bei der Implementierung von I3 ist es notwendig, beide gleichzeitig zu implementieren.
In der Regel bedarf es einer expliziten Implementierung, um auf die ursprüngliche Methode zu verweisen. Die Frage ist, ob in einer Klasse
class C : I1, I2, I3
{
C IN.M();
}
Was bedeutet das in diesem Fall? Was sollte N sein?
Ich schlage vor, dass wir die Implementierung von entweder I1.M oder I2.M (aber nicht beide) zulassen und dies als Implementierung beider behandeln.
Nachteile
- [ ] Jede Sprachänderung muss sich lohnen.
- [ ] Wir sollten sicherstellen, dass die Leistung angemessen ist, auch bei verschachtelten Vererbungshierarchien.
- [ ] Wir sollten sicherstellen, dass Artefakte der Übersetzungsstrategie nicht die Sprachsemantik beeinflussen, selbst wenn neue IL durch alte Compiler verarbeitet werden.
Alternativen
Wir könnten die Sprachregeln leicht lockern, um im Quelltext zuzulassen,
// Possible alternative. This was not implemented.
abstract class Cloneable
{
public abstract Cloneable Clone();
}
class Digit : Cloneable
{
public override Cloneable Clone()
{
return this.Clone();
}
public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types
{
return this;
}
}
Ungelöste Fragen
- [ ] Wie funktionieren APIs, die kompiliert wurden, um dieses Feature in älteren Sprachversionen zu verwenden?
Designbesprechungen
- einige Diskussionen bei https://github.com/dotnet/roslyn/issues/357.
- https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-01-08.md
- Offline-Diskussion über eine Entscheidung zur Unterstützung der Überschreibung von Klassenmethoden nur in C# 9.0.
C# feature specifications