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.
19.1 Allgemein
Eine Schnittstelle definiert einen Vertrag. Eine Klasse oder Struktur, die eine Schnittstelle implementiert, muss ihren Vertrag einhalten. Eine Schnittstelle kann von mehreren Basisschnittstellen erben, und eine Klasse oder Struktur kann mehrere Schnittstellen implementieren.
Schnittstellen können verschiedene Arten von Mitgliedern enthalten, wie in §19.4 beschrieben. Die Schnittstelle selbst kann eine Implementierung für einige oder alle Funktionsmmber bereitstellen, die sie deklariert. Elemente, für die die Schnittstelle keine Implementierung bereitstellt, sind abstrakt. Ihre Implementierungen müssen von Klassen oder Strukturen bereitgestellt werden, die die Schnittstelle implementieren oder abgeleitete Schnittstelle, die eine überschreibende Definition bereitstellen.
Hinweis: In der Vergangenheit hat das Hinzufügen eines neuen Funktionsmitglieds zu einer Schnittstelle Auswirkungen auf alle vorhandenen Verbraucher dieses Schnittstellentyps; es war eine bahnbrechende Änderung. Durch das Hinzufügen von Schnittstellenfunktionsmemlerimplementierungen können Entwickler eine Schnittstelle aktualisieren und gleichzeitig implementieren, dass diese Implementierung außer Kraft setzt. Benutzer der Schnittstelle können die Implementierung als ungebrochene Änderung akzeptieren; Wenn ihre Anforderungen jedoch unterschiedlich sind, können sie die bereitgestellten Implementierungen außer Kraft setzen. Hinweisende
19.2 Schnittstellendeklarationen
19.2.1 Allgemein
Ein interface_declaration ist eine type_declaration (§14.7), die einen neuen Schnittstellentyp deklariert.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
Ein interface_declaration besteht aus einem optionalen Satz von Attributen (§23), gefolgt von einem optionalen Satz von interface_modifiers (§19.2.2), gefolgt von einem optionalen Teilmodifizierer (§15.2.7), gefolgt von dem Schlüsselwort interface und einem Bezeichner , der die Schnittstelle benennt, gefolgt von einer optionalen variant_type_parameter_list Spezifikation (§19.2.3), gefolgt von einer optionalen interface_base Spezifikation (§19.2.4)), gefolgt von einer optionalen type_parameter_constraints_clauseSpezifikation (§15.2.5), gefolgt von einem interface_body (§19.3), optional gefolgt von einem Semikolon.
Eine Schnittstellendeklaration darf keine type_parameter_constraints_clause-Elemente angeben, es sei denn, sie gibt auch variant_type_parameter_list an.
Eine Schnittstellendeklaration, die eine variant_type_parameter_list bereitstellt, ist eine generische Schnittstellendeklaration. Darüber hinaus ist jede Schnittstelle, die in einer generischen Klassendeklaration oder einer generischen Strukturdeklaration geschachtelt ist, selbst eine generische Schnittstellendeklaration, da Typargumente für den enthaltenden Typ bereitgestellt werden müssen, um einen konstruierten Typ (§8.4) zu erstellen.
19.2.2 Schnittstellenmodifizierer
Ein interface_declaration kann optional eine Sequenz von Schnittstellenmodifizierern enthalten:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) ist nur im unsicheren Code (§24) verfügbar.
Es handelt sich um einen Kompilierungsfehler für denselben Modifizierer, der mehrmals in einer Schnittstellendeklaration angezeigt wird.
Der new Modifizierer ist nur für Schnittstellen zulässig, die innerhalb einer Klasse definiert sind. Es gibt an, dass die Schnittstelle ein geerbtes Element mit demselben Namen ausblendet, wie in §15.3.5 beschrieben.
Die public, protected, internalund private Modifizierer steuern die Barrierefreiheit der Schnittstelle. Je nach Kontext, in dem die Schnittstellendeklaration auftritt, sind möglicherweise nur einige dieser Modifizierer zulässig (§7.5.2). Wenn eine Teiltypdeklaration (§15.2.7) eine Barrierefreiheitsspezifikation enthält (über die public, protected, , internalund private Modifizierer), gelten die Regeln in §15.2.2 .
19.2.3 Variant-Typparameterlisten
19.2.3.1 Allgemein
Variantentypparameterlisten können nur für Schnittstellen- und Delegattypen auftreten. Der Unterschied zu gewöhnlichen type_parameter_lists ist die optionale variance_annotation für jeden Typparameter.
variant_type_parameter_list
: '<' variant_type_parameter (',' variant_type_parameter)* '>'
;
variant_type_parameter
: attributes? variance_annotation? type_parameter
;
variance_annotation
: 'in'
| 'out'
;
Wenn die Varianzanmerkung lautetout, wird der Typparameter als kovariant bezeichnet. Wenn die Varianzanmerkung lautetin, wird der Typparameter als kontravariant bezeichnet. Wenn keine Varianzanmerkung vorhanden ist, wird der Typparameter als invariant bezeichnet.
Beispiel: Im Folgenden:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
Xist kovariant,Yist kontravariant undZist invariant.Endbeispiel
Wenn eine generische Schnittstelle in mehreren Teilen deklariert wird (§15.2.3), muss jede Teildeklaration die gleiche Varianz für jeden Typparameter angeben.
19.2.3.2 Varianzsicherheit
Das Vorkommen von Varianzanmerkungen in der Typparameterliste eines Typs schränkt die Orte ein, an denen Typen innerhalb der Typdeklaration auftreten können.
Ein Typ T ist ausgabe-unsicher wenn eine der folgenden Bedingungen zutrifft:
-
Tist ein kontravarianter Typparameter. -
Tist ein Arraytyp mit einem ausgabeunsicheren Elementtyp -
Tstellt einen Schnittstellen- oder DelegattypSᵢ,... Aₑdar, der aus einem generischen TypS<Xᵢ, ... Xₑ>konstruiert wird, bei dem für mindestens einAᵢeine der folgenden Bedingungen zutrifft:-
Xᵢist kovariant oder invariant undAᵢist ausgabeunsicher. -
Xᵢist kontravariant oder invariant undAᵢist eingabeunsicher.
-
Ein Typ T ist eingabeunsicher, wenn eine der folgenden Bedingungen zutrifft:
-
Tist ein kovarianter Typparameter -
Tist ein Arraytyp mit einem eingabeunsicheren Elementtyp -
Tstellt einen Schnittstellen- oder DelegattypS<Aᵢ,... Aₑ>dar, der aus einem generischen TypS<Xᵢ, ... Xₑ>konstruiert wird, bei dem für mindestens einAᵢeine der folgenden Bedingungen zutrifft:-
Xᵢist kovariant oder invariant undAᵢist eingabeunsicher. -
Xᵢist kontravariant oder invariant undAᵢist ausgabeunsicher.
-
Intuitiv ist ein ausgabeunsicherer Typ an einer Ausgabeposition verboten, und ein eingabeunsicherer Typ ist an einer Eingabeposition verboten.
Ein Typ ist ausgabesicher, wenn er nicht ausgabeunsicher ist und eingabesicher ist, wenn er nicht eingabeunsicher ist.
19.2.3.3 Varianzumwandlung
Der Zweck von Varianzkennzeichnungen besteht darin, flexiblere (aber dennoch typsichere) Konvertierungen in Schnittstellen- und Delegattypen bereitzustellen. Zu diesem Zweck verwenden die Definitionen impliziter (§10.2) und expliziter Konvertierungen (§10.3) den Begriff der Varianzkonvertierbarkeit, der wie folgt definiert ist:
Ein Typ T<Aᵢ, ..., Aᵥ> ist varianzkonvertierbar in einen Typ T<Bᵢ, ..., Bᵥ>, wenn T entweder eine Schnittstelle oder ein Delegattyp ist, der mit den Variante-Typparametern T<Xᵢ, ..., Xᵥ> deklariert ist, und für jeden Variante-Typparameter Xᵢ einer der folgenden erfüllt ist:
-
Xᵢist kovariant, und es gibt eine implizite Referenz- oder Identitätskonversion vonAᵢzuBᵢ. -
Xᵢist kontravariant und es besteht eine implizite Referenz- oder Identitätskonvertierung vonBᵢzuAᵢ. -
Xᵢist invariant und es existiert eine Identitätskonversion vonAᵢnachBᵢ
19.2.4 Basisschnittstellen
Eine Schnittstelle kann von null oder mehr Schnittstellentypen erben, die als explizite Basisschnittstellender Schnittstelle bezeichnet werden. Wenn eine Schnittstelle über eine oder mehrere explizite Basisschnittstellen verfügt, folgt in der Deklaration dieser Schnittstelle der Schnittstellenbezeichner ein Doppelpunkt und eine durch Trennzeichen getrennte Liste der Basisschnittstellentypen.
Eine abgeleitete Schnittstelle kann neue Member deklarieren, die geerbte Member (§7.7.2.3) ausblenden, die in Basisschnittstellen deklariert sind, oder explizit geerbte Member (§19.6.2) implementieren, die in Basisschnittstellen deklariert sind.
interface_base
: ':' interface_type_list
;
Die expliziten Basisschnittstellen können Schnittstellentypen (§8.4, §19.2) konstruiert werden. Eine Basisschnittstelle kann nicht eigenständig ein Typparameter sein, obwohl sie die Typparameter einbeziehen kann, die sich im Bereich befinden.
Für einen konstruierten Schnittstellentyp werden die expliziten Basisschnittstellen gebildet, indem die expliziten Basisschnittstellendeklarationen der generischen Typdeklaration herangezogen werden. Dabei wird für jeden type_parameter in der Basisschnittstellendeklaration das entsprechende type_argument des konstruierten Typs eingesetzt.
Die expliziten Basisschnittstellen einer Schnittstelle müssen mindestens so zugänglich sein wie die Schnittstelle selbst (§7.5.5).
Hinweis: Beispielsweise ist es ein Fehler zur Kompilierungszeit, wenn eine
private- oderinternal-Schnittstelle in der interface_base einerpublic-Schnittstelle angegeben wird. Hinweisende
Es ist ein Kompilierungsfehler, wenn eine Schnittstelle direkt oder indirekt von sich selbst erbt.
Die Basisschnittstelleneiner Schnittstelle sind die expliziten Basisschnittstellen und ihre Basisschnittstellen. Anders ausgedrückt: Der Satz von Basisschnittstellen ist der vollständige transitive Abschluss der expliziten Basisschnittstellen, ihrer expliziten Basisschnittstellen usw. Eine Schnittstelle erbt alle Member ihrer Basisschnittstellen.
Beispiel: Im folgenden Code
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}die Basisschnittstellen von
IComboBoxsindIControl,ITextBoxundIListBox. Anders ausgedrückt: Die SchnittstelleIComboBoxoben erbt die MemberSetTextundSetItemssowiePaint.Endbeispiel
Von einem konstruierten generischen Typ geerbte Member werden nach der Typersetzung geerbt. Das heißt, alle Bestandteiltypen im Element haben die Typparameter der Basisklassendeklaration durch die entsprechenden Typargumente ersetzt, die in der class_base Spezifikation verwendet werden.
Beispiel: Im folgenden Code
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }die Schnittstelle
IDerivederbt dieCombineMethode, nachdem der TypparameterTdurchstring[,]ersetzt wurde.Endbeispiel
Eine Klasse oder Struktur, die eine Schnittstelle implementiert, implementiert auch implizit alle Basisschnittstellen der Schnittstelle.
Die Handhabung von Schnittstellen zu mehreren Teilen einer Teilschnittstellendeklaration (§15.2.7) wird in §15.2.4.3 weiter erläutert.
Jede Basisschnittstelle einer Schnittstelle muss ausgabesicher sein (§19.2.3.2).
19.3 Schnittstellentext
Die interface_body einer Schnittstelle definiert die Member der Schnittstelle.
interface_body
: '{' interface_member_declaration* '}'
;
19.4 Schnittstellenmitglieder
19.4.1 Allgemein
Die Member einer Schnittstelle sind die Elemente, die von den Basisschnittstellen geerbt werden, und die Elemente, die von der Schnittstelle selbst deklariert wurden.
interface_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| static_constructor_declaration
| operator_declaration
| type_declaration
;
Diese Klausel erweitert die Beschreibung von Membern in Klassen (§15.3) mit Einschränkungen für Schnittstellen. Die Interface-Member werden mit member_declarationmit den folgenden zusätzlichen Regeln deklariert:
- Ein finalizer_declaration ist nicht zulässig.
- Instanzkonstruktoren, constructor_declarations, sind nicht zulässig.
- Alle Schnittstellenmitglieder haben implizit öffentlichen Zugriff; Ein expliziter Zugriffsmodifizierer (§7.5.2) ist jedoch zulässig, außer bei statischen Konstruktoren (§15.12).
- Der
abstractModifizierer wird für Schnittstellenfunktionsmmber ohne Körper impliziert; dieser Modifizierer kann explizit angegeben werden. - Ein Member der Schnittstelleninstanzfunktion, deren Deklaration einen Textkörper enthält, ist implizit
virtualein Element, es sei denn, dersealedprivateModifizierer wird verwendet. DervirtualModifizierer kann explizit angegeben werden. - Ein
privateOdersealedFunktionselement einer Schnittstelle muss über einen Textkörper verfügen. - Ein
privateFunktionselement darf nicht über den Modifizierersealedverfügen. - Eine abgeleitete Schnittstelle kann ein abstraktes oder virtuelles Element außer Kraft setzen, das in einer Basisschnittstelle deklariert ist.
- Ein explizit implementiertes Funktionselement darf nicht über den Modifizierer
sealedverfügen.
Einige Deklarationen, z. B. constant_declaration (§15.4) haben keine Einschränkungen in Schnittstellen.
Die geerbten Member einer Schnittstelle sind ausdrücklich nicht Teil des Deklarationsabschnitts der Schnittstelle. Daher kann eine Schnittstelle ein Element mit demselben Namen oder derselben Signatur wie ein geerbtes Element deklarieren. Wenn dies geschieht, heißt es, dass das abgeleitete Schnittstellenmitglied das Basisschnittstellenelement verbirgt. Das Ausblenden eines geerbten Elements wird nicht als Fehler betrachtet, führt jedoch zu einer Warnung (§7.7.2.3).
Wenn ein new Modifizierer in einer Deklaration enthalten ist, die kein geerbtes Element ausblendet, wird dementsprechend eine Warnung ausgegeben.
Hinweis: Die Mitglieder in der Klasse
objectsind nicht streng genommen Member einer Schnittstelle (§19.4). Die Mitglieder in der Klasseobjectsind jedoch in einem beliebigen Schnittstellentyp (§12.5) über die Mitgliedersuche verfügbar. Hinweisende
Die Gruppe von Mitgliedern einer Schnittstelle, die in mehreren Teilen deklariert ist (§15.2.7) ist die Vereinigung der mitglieder, die in jedem Teil deklariert sind. Die Körper aller Teile der Schnittstellendeklaration haben denselben Deklarationsbereich (§7.3), und der Umfang jedes Mitglieds (§7.7) erstreckt sich auf die Körper aller Teile.
Beispiel: Betrachten Sie eine Schnittstelle
IAmit einer Implementierung für ein ElementMund eine EigenschaftP. Ein ImplementierungstypCstellt weder eine Implementierung noch eine Implementierung bereitMP. Auf sie muss über einen Verweis zugegriffen werden, dessen Kompilierungszeittyp eine Schnittstelle ist, die implizit in oderIAumsetzbarIBist. Diese Member werden nicht über die Membersuche für eine Variable vom TypCgefunden.interface IA { public int P { get { return 10; } } public void M() { Console.WriteLine("IA.M"); } } interface IB : IA { public new int P { get { return 20; } } void IA.M() { Console.WriteLine("IB.M"); } } class C : IB { } class Test { public static void Main() { C c = new C(); ((IA)c).M(); // cast needed Console.WriteLine($"IA.P = {((IA)c).P}"); // cast needed Console.WriteLine($"IB.P = {((IB)c).P}"); // cast needed } }Innerhalb der Schnittstellen
IAundIBdes MembersMkann direkt über den Namen zugegriffen werden. Innerhalb der MethodeMainkönnen wir jedoch nicht schreiben oderc.M(),c.Pda diese Namen nicht sichtbar sind. Um sie zu finden, werden Umwandlungen in den entsprechenden Schnittstellentyp benötigt. Die Deklaration vonMinIBverwendet explizite Syntax der Schnittstellenimplementierung. Dies ist erforderlich, damit diese Methode dasIAElement außer Kraft setzt; der Modifiziereroverridekann nicht auf ein Funktionselement angewendet werden. Endbeispiel
19.4.2 Schnittstellenfelder
Diese Klausel erweitert die Beschreibung von Feldern in Klassen §15.5 für Felder, die in Schnittstellen deklariert sind.
Schnittstellenfelder werden mithilfe field_declarations (§15.5.1) mit den folgenden zusätzlichen Regeln deklariert:
- Es handelt sich um einen Kompilierungszeitfehler für field_declaration zum Deklarieren eines Instanzfelds.
Beispiel: Das folgende Programm enthält statische Member verschiedener Arten:
public interface IX { public const int Constant = 100; protected static int field; static IX() { Console.WriteLine("static members initialized"); Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}"); field = 50; Console.WriteLine("static constructor has run"); } } public class Test: IX { public static void Main() { Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}"); } }Die erzeugte Ausgabe ist
static members initialized constant = 100, field = 0 static constructor has run constant = 100, field = 50Endbeispiel
Informationen zur Zuordnung und Initialisierung statischer Felder finden Sie unter §19.4.8 .
19.4.3 Schnittstellenmethoden
Diese Klausel erweitert die Beschreibung der Methoden in Klassen §15.6 für methoden, die in Schnittstellen deklariert sind.
Schnittstellenmethoden werden mit method_declaration s(§15.6) deklariert. Die Attribute, return_type, ref_return_type, Bezeichner und parameter_list einer Schnittstellenmethodendeklaration haben die gleiche Bedeutung wie die attribute einer Methodendeklaration in einer Klasse. Schnittstellenmethoden weisen die folgenden zusätzlichen Regeln auf:
method_modifier darf nicht enthalten
override.Eine Methode, deren Textkörper ein Semikolon (
;) istabstract; derabstractModifizierer ist nicht erforderlich, ist jedoch zulässig.Eine Schnittstellenmethodendeklaration mit einem Blocktext oder Ausdruckstext als method_body ist
virtual; dervirtualModifizierer ist nicht erforderlich, ist jedoch zulässig.Ein method_declaration darf nicht type_parameter_constraints_clausehaben, es sei denn, er hat auch eine type_parameter_list.
Die Liste der Anforderungen für gültige Kombinationen von Modifizierern, die für eine Klassenmethode angegeben sind, wird wie folgt erweitert:
- Eine statische Deklaration, die nicht extern ist, darf einen Blocktext oder Ausdruckstext als method_body aufweisen.
- Eine virtuelle Deklaration, die nicht extern ist, hat einen Blocktext oder Ausdruckstext als method_body.
- Eine private Erklärung, die nicht extern ist, darf einen Blockkörper oder Ausdruckskörper als method_body haben.
- Eine versiegelte Deklaration, die nicht extern ist, hat einen Blockkörper oder Ausdruckskörper als method_body.
- Eine asynchrone Deklaration muss einen Blocktext oder einen Ausdruckstext als method_body haben.
Alle Parametertypen einer Schnittstellenmethode müssen eingabesicher sein (§19.2.3.2), und der Rückgabetyp muss entweder
voidoder ausgabesicher sein.Alle Ausgabe- oder Referenzparametertypen müssen auch ausgabesicher sein.
Hinweis: Ausgabeparameter müssen aufgrund allgemeiner Implementierungseinschränkungen eingabesicher sein. Hinweisende
Jede Klassentypeinschränkung, Schnittstellentypeinschränkung und Typparametereinschränkung für alle Typparameter der Methode müssen eingabesicher sein.
Diese Regeln stellen sicher, dass jede kovariante oder kontravariante Verwendung der Schnittstelle typsicher bleibt.
Beispiel:
interface I<out T> { void M<U>() where U : T; // Error }Dies ist falsch formatiert, da die Verwendung von
Tals Typparametereinschränkung fürUnicht eingabesicher ist.Wäre diese Einschränkung nicht vorhanden, wäre es möglich, die Typsicherheit auf folgende Weise zu verletzen:
interface I<out T> { void M<U>() where U : T; } class B {} class D : B {} class E : B {} class C : I<D> { public void M<D>() {...} } ... I<B> b = new C(); b.M<E>();Dies ist eigentlich ein Aufruf an
C.M<E>. Aber dieser Aufruf erfordert, dassEvonDabgeleitet wird, daher würde die Typsicherheit hier verletzt.Endbeispiel
Hinweis: Siehe §19.4.2 für ein Beispiel, das nicht nur eine statische Methode mit einer Implementierung anzeigt, sondern wie diese Methode aufgerufen
Mainwird und über den richtigen Rückgabetyp und die richtige Signatur verfügt, ist sie auch ein Einstiegspunkt. Hinweisende
Eine virtuelle Methode mit in einer Schnittstelle deklarierter Implementierung kann außer Kraft gesetzt werden, um in einer abgeleiteten Schnittstelle abstrahieren zu können. Dies wird als Erneutes Abstraktion bezeichnet.
Beispiel:
interface IA { void M() { Console.WriteLine("IA.M"); } } interface IB: IA { abstract void IA.M(); // reabstraction of M }Dies ist nützlich bei abgeleiteten Schnittstellen, bei denen die Implementierung einer Methode unangemessen ist und eine geeignetere Implementierung durch die Implementierung von Klassen bereitgestellt werden sollte. Endbeispiel
19.4.4 Schnittstelleneigenschaften
Diese Klausel erweitert die Beschreibung von Eigenschaften in Klassen §15.7 für in Schnittstellen deklarierte Eigenschaften.
Schnittstelleneigenschaften werden mithilfe property_declarations (§15.7.1) mit den folgenden zusätzlichen Regeln deklariert:
property_modifier dürfen nicht enthalten
override.Eine explizite Schnittstellenmitgliedsimplementierung darf keine accessor_modifier (§15.7.3) enthalten.
Eine abgeleitete Schnittstelle kann explizit eine abstrakte Schnittstelleneigenschaft implementieren, die in einer Basisschnittstelle deklariert ist.
Hinweis: Da eine Schnittstelle keine Instanzfelder enthalten kann, kann eine Schnittstelleneigenschaft keine automatische Instanzeigenschaft sein, da dies die Deklaration impliziter ausgeblendeter Instanzenfelder erfordert. Hinweisende
Der Typ einer Schnittstelleneigenschaft muss ausgabesicher sein, wenn ein Get-Accessor vorhanden ist und eingabesicher sein muss, wenn ein festgelegter Accessor vorhanden ist.
Eine Schnittstellenmethodendeklaration mit einem Blocktext oder Ausdruckstext als method_body ist
virtual; dervirtualModifizierer ist nicht erforderlich, ist jedoch zulässig.Eine Instanz property_declaration , die keine Implementierung hat, ist
abstract; derabstractModifizierer ist nicht erforderlich, ist jedoch zulässig. Es gilt niemals als automatisch implementierte Eigenschaft (§15.7.4).
19.4.5 Schnittstellenereignisse
Diese Klausel erweitert die Beschreibung von Ereignissen in Klassen §15.8 für Ereignisse, die in Schnittstellen deklariert sind.
Schnittstellenereignisse werden mit event_declarations (§15.8.1) mit den folgenden zusätzlichen Regeln deklariert:
-
event_modifier dürfen nicht enthalten
override. - Eine abgeleitete Schnittstelle kann ein abstraktes Schnittstellenereignis implementieren, das in einer Basisschnittstelle deklariert ist (§15.8.5).
- Es handelt sich um einen Kompilierungszeitfehler für variable_declarators in einer Instanz , die event_declaration alle variable_initializerenthalten soll.
- Ein Instanzereignis mit den
virtualOdersealedModifizierern muss Accessoren deklarieren. Es wird niemals als automatisch implementiertes feldähnliches Ereignis (§15.8.2) betrachtet. - Ein Instanzereignis mit dem
abstractModifizierer darf keine Accessoren deklarieren. - Der Typ eines Schnittstellenereignisses muss eingabesicher sein.
19.4.6 Schnittstellenindexer
Diese Klausel erweitert die Beschreibung von Indexern in Klassen §15.9 für in Schnittstellen deklarierte Indexer.
Schnittstellenindexer werden mit indexer_declarations (§15.9) mit den folgenden zusätzlichen Regeln deklariert:
indexer_modifier dürfen nicht enthalten
override.Eine indexer_declaration , die einen Ausdruckstext enthält oder einen Accessor mit einem Blocktext oder Ausdruckstext enthält, ist
virtual; dervirtualModifizierer ist nicht erforderlich, aber zulässig.Ein indexer_declaration , dessen Accessorkörper Semikolons (
;) sindabstract; derabstractModifizierer ist nicht erforderlich, ist jedoch zulässig.Alle Parametertypen eines Schnittstellenindexers müssen eingabesicher sein (§19.2.3.2).
Alle Ausgabe- oder Referenzparametertypen müssen auch ausgabesicher sein.
Hinweis: Ausgabeparameter müssen aufgrund allgemeiner Implementierungseinschränkungen eingabesicher sein. Hinweisende
Der Typ eines Schnittstellenindexers muss ausgabesicher sein, wenn ein Get-Accessor vorhanden ist und eingabesicher sein muss, wenn ein festgelegter Accessor vorhanden ist.
19.4.7 Schnittstellenoperatoren
Diese Klausel erweitert die Beschreibung operator_declaration Member in Klassen §15.10 für Operatoren, die in Schnittstellen deklariert sind.
Eine operator_declaration in einer Schnittstelle ist die Implementierung (§19.1).
Es handelt sich um einen Kompilierungsfehler für eine Schnittstelle, um einen Konvertierungs-, Gleichheits- oder Ungleichheitsoperator zu deklarieren.
19.4.8 Statische Schnittstellenkonstruktoren
Diese Klausel erweitert die Beschreibung statischer Konstruktoren in Klassen §15.12 für statische Konstruktoren, die in Schnittstellen deklariert sind.
Der statische Konstruktor für eine geschlossene (§8.4.3)-Schnittstelle wird höchstens einmal in einer bestimmten Anwendungsdomäne ausgeführt. Die Ausführung eines statischen Konstruktors wird durch die erste der folgenden Aktionen ausgelöst, die in einer Anwendungsdomäne auftreten:
- Auf alle statischen Member der Schnittstelle wird verwiesen.
- Bevor die
MainMethode für eine Schnittstelle aufgerufen wird, die dieMainMethode (§7.1) enthält, in der die Ausführung beginnt. - Diese Schnittstelle stellt eine Implementierung für ein Mitglied bereit, und auf diese Implementierung wird als spezifischste Implementierung (§19.4.10) für dieses Mitglied zugegriffen.
Hinweis: Wenn keine der vorherigen Aktionen ausgeführt wird, kann der statische Konstruktor für eine Schnittstelle nicht für ein Programm ausgeführt werden, in dem Instanzen von Typen, die die Schnittstelle implementieren, erstellt und verwendet werden. Hinweisende
Zum Initialisieren eines neuen geschlossenen Schnittstellentyps wird zunächst ein neuer Satz statischer Felder für diesen bestimmten geschlossenen Typ erstellt. Jedes der statischen Felder wird mit dem Standardwert initialisiert. Als Nächstes werden die statischen Feldinitialisierer für diese statischen Felder ausgeführt. Schließlich wird der statische Konstruktor ausgeführt.
Hinweis: Ein Beispiel für die Verwendung verschiedener Arten statischer Elemente (einschließlich einer Main-Methode) in einer Schnittstelle finden Sie unter §19.4.2 . Hinweisende
19.4.9 Geschachtelte Schnittstellentypen
Diese Klausel erweitert die Beschreibung geschachtelter Typen in Klassen §15.3.9 für geschachtelte Typen, die in Schnittstellen deklariert sind.
Fehler beim Deklarieren eines Klassentyps, eines Strukturtyps oder eines Enumerationstyps innerhalb des Bereichs eines Typparameters, der mit einem variance_annotation deklariert wurde (§19.2.3.1).
Beispiel: Die folgende Deklaration
Cist ein Fehler.interface IOuter<out T> { class C { } // error: class declaration within scope of variant type parameter 'T' }Endbeispiel
19.4.10 spezifische Implementierung
Jede Klasse und Jede Struktur muss eine spezifische Implementierung für jedes virtuelle Element haben, das in allen Schnittstellen deklariert ist, die von diesem Typ unter den Implementierungen implementiert werden, die in dem Typ oder seinen direkten und indirekten Schnittstellen angezeigt werden. Die spezifischste Implementierung ist eine einzigartige Implementierung, die spezifischer ist als jede andere Implementierung.
Hinweis: Die spezifischste Implementierungsregel stellt sicher, dass eine Mehrdeutigkeit, die sich aus der Vererbung von Rautenschnittstellen ergibt, explizit vom Programmierer an der Stelle gelöst wird, an der der Konflikt auftritt. Hinweisende
Für einen Typ T , der eine Struktur oder eine Klasse ist, die Schnittstellen I2 implementiert und I3, wobei I2 beide I3 direkt oder indirekt von der Schnittstelle I abgeleitet werden, die ein Element Mdeklariert, lautet die spezifischeste Implementierung von M :
- Wenn
Teine Implementierung deklariertI.Mwird, ist diese Implementierung die spezifischste Implementierung. - Andernfalls deklariert die am häufigsten abgeleitete Basisklasse, wenn
Tes sich um eine Klasse handelt und eine direkte oder indirekte Basisklasse eine Implementierung deklariertI.M.T - Andernfalls handelt es sich bei Schnittstellen,
I2die vonI3TI3direkt oder indirekt implementiert werden,I2um eine spezifischere Implementierung als .I3.MI2.M - Andernfalls tritt weder
I2.MI3.Mnoch spezifischer auf, und ein Fehler tritt auf.
Beispiel:
interface IA { void M() { Console.WriteLine("IA.M"); } } interface IB : IA { void IA.M() { Console.WriteLine("IB.M"); } } interface IC: IA { void IA.M() { Console.WriteLine("IC.M"); } } abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M' abstract class D: IA, IB, IC // OK { public abstract void M(); }Die spezifischste Implementierungsregel stellt sicher, dass ein Konflikt (d. h. eine Mehrdeutigkeit, die sich aus der Rautenvererbung ergibt) explizit vom Programmierer an der Stelle gelöst wird, an der der Konflikt entsteht. Endbeispiel
19.4.11 Schnittstellenmitgliedszugriff
Auf Schnittstellenmember wird über Memberzugriffsausdrücke (§12.8.7) und Indexerzugriff (§12.8.12.4) des Formulars I.M zugegriffen, wobei I[A]es sich um I einen Schnittstellentyp handelt, ist eine Konstante, M ein Feld, eine Methode, eigenschaft oder ein Ereignis dieses Schnittstellentyps und A eine Indexerargumentliste.
In einer Klasse Dmit direkter oder indirekter BasisklasseB, bei der B die Schnittstelle I direkt oder indirekt implementiert und I eine Methode M()definiert wird, ist der Ausdruck nur gültig, wenn base.M() der Ausdruck base.M() statisch (§12.3) an eine Implementierung eines M() Klassentyps gebunden wird.
Bei Schnittstellen, die streng single-vererbung sind (jede Schnittstelle in der Vererbungskette hat genau null oder eine direkte Basisschnittstelle), sind die Auswirkungen der Membersuche (§12.5), Methodenaufrufe (§12.8.10.2) und Indexerzugriff (§12.8.12.4) identisch mit Klassen und Strukturen: Weniger abgeleitete Member verbergen weniger abgeleitete Member mit demselben Namen oder derselben Signatur. Bei Mehrfachvererbungsschnittstellen können jedoch Mehrdeutigkeiten auftreten, wenn zwei oder mehr nicht verwandte Basisschnittstellen Member mit demselben Namen oder derselben Signatur deklarieren. Diese Unterliste zeigt mehrere Beispiele, von denen einige zu Mehrdeutigkeiten und anderen führen, die nicht. In allen Fällen können explizite Umwandlungen verwendet werden, um die Mehrdeutigkeiten aufzulösen.
Beispiel: Im folgenden Code
interface IList { int Count { get; set; } } interface ICounter { int Count { get; set; } } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count } }die erste Anweisung verursacht einen Kompilierungszeitfehler, da die Elementsuche (§12.5) von
CountinIListCountermehrdeutig ist. Wie im Beispiel dargestellt, wird die Mehrdeutigkeit durch Umwandlungxin den entsprechenden Basisschnittstellentyp aufgelöst. Für solche Umwandlungen fallen keine Laufzeitkosten an: Sie bestehen lediglich darin, die Instanz zur Kompilierungszeit als weniger stark abgeleiteten Typ anzuzeigen.Endbeispiel
Beispiel: Im folgenden Code
interface IInteger { void Add(int i); } interface IDouble { void Add(double d); } interface INumber : IInteger, IDouble {} class C { void Test(INumber n) { n.Add(1); // Invokes IInteger.Add n.Add(1.0); // Only IDouble.Add is applicable ((IInteger)n).Add(1); // Only IInteger.Add is a candidate ((IDouble)n).Add(1); // Only IDouble.Add is a candidate } }der Aufruf
n.Add(1)wähltIInteger.Addaus, indem Überlastungsregeln von §12.6.4 angewendet werden. Ebenso wirdn.Add(1.0)durch den Aufruf vonIDouble.Addausgewählt. Wenn explizite Umwandlungen eingefügt werden, gibt es nur eine Kandidatenmethode und somit keine Mehrdeutigkeit.Endbeispiel
Beispiel: Im folgenden Code
interface IBase { void F(int i); } interface ILeft : IBase { new void F(int i); } interface IRight : IBase { void G(); } interface IDerived : ILeft, IRight {} class A { void Test(IDerived d) { d.F(1); // Invokes ILeft.F ((IBase)d).F(1); // Invokes IBase.F ((ILeft)d).F(1); // Invokes ILeft.F ((IRight)d).F(1); // Invokes IBase.F } }Das
IBase.F-Mitglied ist durch dasILeft.F-Mitglied ausgeblendet. Der Aufrufd.F(1)wählt daherILeft.Faus, auch wennIBase.Fin dem Zugriffspfad, der durchIRightführt, nicht verborgen zu sein scheint.Die intuitive Regel zum Ausblenden in Schnittstellen mit mehreren Vererbungen ist einfach dies: Wenn ein Element in einem beliebigen Zugriffspfad ausgeblendet ist, wird es in allen Zugriffspfaden ausgeblendet. Da der Zugriffspfad von
IDerivedzuILeftzuIBaseIBase.Fausblendet, wird das Mitglied auch im Zugriffspfad vonIDerivedzuIRightzuIBaseausgeblendet.Endbeispiel
19.5 Qualifizierte Schnittstellenmitgliedsnamen
Ein Schnittstellenelement wird manchmal durch den Namen des qualifizierten Schnittstellenmitglieds bezeichnet. Der qualifizierte Name eines Schnittstellenelements besteht aus dem Namen der Schnittstelle, in der das Element deklariert wird, gefolgt von einem Punkt, gefolgt vom Namen des Elements. Der qualifizierte Name eines Mitglieds verweist auf die Schnittstelle, in der das Element deklariert wird.
Beispiel: Aufgrund der Deklarationen
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }der qualifizierte Name von
PaintistIControl.Paint, und der qualifizierte Name von SetText istITextBox.SetText. Im obigen Beispiel ist es nicht möglich, aufPaintalsITextBox.Paintzu verweisen.Endbeispiel
Wenn eine Schnittstelle Teil eines Namespaces ist, kann ein qualifizierter Schnittstellenmemmname den Namespacenamen enthalten.
Beispiel:
namespace GraphicsLib { interface IPolygon { void CalculateArea(); } }Innerhalb des
GraphicsLib-Namespaces sind sowohlIPolygon.CalculateAreaals auchGraphicsLib.IPolygon.CalculateAreaqualifizierte Schnittstellenmitgliedsnamen für dieCalculateArea-Methode.Endbeispiel
19.6 Schnittstellenimplementierungen
19.6.1 Allgemein
Schnittstellen können von Klassen und Strukturen implementiert werden. Um anzugeben, dass eine Klasse oder Struktur direkt eine Schnittstelle implementiert, ist die Schnittstelle in der Basisklassenliste der Klasse oder Struktur enthalten.
Eine Klasse oder Struktur C , die eine Schnittstelle I implementiert, muss eine Implementierung für jedes in diesem Element deklarierte I Element bereitstellen oder erben, auf das C zugegriffen werden kann. Öffentliche Mitglieder von I können in öffentlichen Mitgliedern von Cdefiniert werden. Nicht öffentliche Mitglieder, die in I denen sie zugänglich C sind, können mithilfe C der expliziten Schnittstellenimplementierung (§19.6.2) definiert werden.
Ein Element in einem abgeleiteten Typ, der die Schnittstellenzuordnung (§19.6.5) erfüllt, aber nicht das entsprechende Basisschnittstellenmemm implementiert, führt ein neues Element ein. Dies tritt auf, wenn die explizite Schnittstellenimplementierung erforderlich ist, um das Schnittstellenelement zu definieren.
Beispiel:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }Endbeispiel
Eine Klasse oder Struktur, die direkt eine Schnittstelle implementiert, implementiert auch implizit alle Basisschnittstellen der Schnittstelle. Dies gilt auch, wenn die Klasse oder Struktur nicht explizit alle Basisschnittstellen in der Basisklassenliste auflistet.
Beispiel:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }Hier implementiert die Klasse
TextBoxsowohlIControlals auchITextBox.Endbeispiel
Wenn eine Klasse C direkt eine Schnittstelle implementiert, implementieren alle von C der Schnittstelle abgeleiteten Klassen implizit die Schnittstelle.
Die in einer Klassendeklaration angegebenen Basisschnittstellen können Schnittstellentypen (§8.4, §19.2) konstruiert werden.
Beispiel: Der folgende Code veranschaulicht, wie eine Klasse konstruierte Schnittstellentypen implementieren kann:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}Endbeispiel
Die Basisschnittstellen einer generischen Klassendeklaration erfüllen die in §19.6.3 beschriebene Eindeutigkeitsregel.
19.6.2 Explizite Schnittstellenmemimplementierungen
Zur Implementierung von Schnittstellen kann eine Klasse, struktur oder Schnittstelle explizite Schnittstellenmememimplementierungendeklarieren. Eine explizite Schnittstellenmemberimplementierung ist eine Methoden-, Eigenschafts-, Ereignis- oder Indexerdeklaration, die auf einen qualifizierten Schnittstellenmitgliedsnamen verweist. Eine Klasse oder Struktur, die ein nicht öffentliches Mitglied in einer Basisschnittstelle implementiert, muss eine explizite Schnittstellenmemimplementierung deklarieren. Eine Schnittstelle, die ein Mitglied in einer Basisschnittstelle implementiert, muss eine explizite Schnittstellenmememerimplementierung deklarieren.
Ein abgeleitetes Schnittstellenelement, das die Schnittstellenzuordnung (§19.6.5) erfüllt, blendet das Basisschnittstellenelement (§7.7.2) aus. Der Compiler gibt eine Warnung aus, es sei denn, der new Modifizierer ist vorhanden.
Beispiel:
interface IList<T> { T[] GetElements(); } interface IDictionary<K, V> { V this[K key] { get; } void Add(K key, V value); } class List<T> : IList<T>, IDictionary<int, T> { public T[] GetElements() {...} T IDictionary<int, T>.this[int index] {...} void IDictionary<int, T>.Add(int index, T value) {...} }Hier sind
IDictionary<int,T>.thisundIDictionary<int,T>.Addexplizite Schnittstellenmitgliedimplementierungen.Endbeispiel
Beispiel: In einigen Fällen ist der Name eines Schnittstellenelements möglicherweise nicht für die implementierungsklasse geeignet, in diesem Fall kann das Schnittstellenmememm mithilfe einer expliziten Schnittstellenmememerimplementierung implementiert werden. Eine Klasse, die eine Dateiabstraktion implementiert, würde z. B. wahrscheinlich eine
CloseMemberfunktion implementieren, die zur Freigabe der Dateiressource dient, und dieDisposeMethode derIDisposableSchnittstelle mithilfe einer expliziten Schnittstellenimplementierung implementieren.interface IDisposable { void Dispose(); } class MyFile : IDisposable { void IDisposable.Dispose() => Close(); public void Close() { // Do what's necessary to close the file System.GC.SuppressFinalize(this); } }Endbeispiel
Es ist nicht möglich, mithilfe des qualifizierten Namens eines Schnittstellenmitglieds in einem Methodenaufruf, Eigenschaftszugriff, Ereigniszugriff oder Indexerzugriff auf eine explizite Schnittstellenmitgliedimplementierung zuzugreifen. Auf eine explizite Elementimplementierung für Schnittstelleninstanzen kann nur über eine Schnittstelleninstanz zugegriffen werden. In diesem Fall wird einfach durch den Membernamen verwiesen. Auf eine explizite Implementierung statischer Schnittstellenmitglieder kann nur über den Schnittstellennamen zugegriffen werden.
Es handelt sich um einen Kompilierungsfehler, wenn eine explizite Schnittstellenmemberimplementierung andere Modifizierer (§15.6) als extern oder async enthält.
Eine explizite Schnittstellenmethodenimplementierung erbt alle Typparametereinschränkungen von der Schnittstelle.
Eine type_parameter_constraints_clause für eine explizite Schnittstellenmethoden-Implementierung darf nur aus den class oder structprimary_constraints bestehen, die auf type_parameters angewendet werden, die gemäß den vererbten Einschränkungen entweder als Referenz- oder Werttypen bekannt sind. Jeder Typ des Formulars T? in der Signatur der implementierung der expliziten Schnittstellenmethode, bei der T es sich um einen Typparameter handelt, wird wie folgt interpretiert:
- Wenn eine
classEinschränkung für den TypparameterThinzugefügt wird,T?ist ein nullabler Verweistyp; andernfalls - Wenn entweder keine zusätzliche Einschränkung vorhanden ist oder eine
structEinschränkung hinzugefügt wird, ist für den TypparameterTeinT?Nullwerttyp vorhanden.
Beispiel: Im Folgenden wird veranschaulicht, wie die Regeln funktionieren, wenn Typparameter beteiligt sind:
#nullable enable interface I { void Foo<T>(T? value) where T : class; void Foo<T>(T? value) where T : struct; } class C : I { void I.Foo<T>(T? value) where T : class { } void I.Foo<T>(T? value) where T : struct { } }Ohne die Typparameter-Beschränkung
where T : classkann die Basismethode mit dem referenztypisierten Typparameter nicht außer Kraft gesetzt werden. Endbeispiel
Hinweis: Explizite Implementierungen von Schnittstellenmitgliedern haben andere Zugänglichkeitseigenschaften als andere Mitglieder. Da auf explizite Schnittstellenimplementierungen niemals über einen qualifizierten Schnittstellenmitgliedsnamen in einem Methodenaufruf oder einem Eigenschaftszugriff zugegriffen werden kann, sind sie gewissermaßen privat. Da sie jedoch über die Schnittstelle aufgerufen werden können, sind sie auch so öffentlich wie die Schnittstelle, in der sie deklariert werden. Explizite Schnittstellenmemberimplementierungen dienen zwei Hauptzwecken.
- Da auf explizite Schnittstellenmitgliedimplementierungen nicht über Instanzen einer Klasse oder einer Struktur zugegriffen werden kann, können Schnittstellenimplementierungen von der öffentlichen Schnittstelle einer Klasse oder einer Struktur ausgeschlossen werden. Dies ist besonders nützlich, wenn eine Klasse oder Struktur eine interne Schnittstelle implementiert, die für einen Verbraucher dieser Klasse oder Struktur nicht von Interesse ist.
- Explizite Schnittstellenmemberimplementierungen ermöglichen die Disambiguierung von Schnittstellenmembern mit derselben Signatur. Ohne explizite Schnittstellenmemberimplementierungen wäre es für eine Klasse, Struktur oder Schnittstelle unmöglich, unterschiedliche Implementierungen von Schnittstellenmembern mit derselben Signatur und demselben Rückgabetyp zu haben, wie es unmöglich wäre, dass eine Klasse, Struktur oder Schnittstelle eine Implementierung auf allen Schnittstellenmembern mit derselben Signatur, aber mit unterschiedlichen Rückgabetypen aufweisen kann.
Hinweisende
Damit eine explizite Schnittstellenmemimplementierung gültig ist, muss die Klasse, Struktur oder Schnittstelle eine Schnittstelle in ihrer Basisklasse oder Basisschnittstellenliste benennen, die ein Mitglied enthält, dessen qualifizierter Schnittstellenmememmname, Typ, Anzahl von Typparametern und Parametertypen genau mit denen der expliziten Schnittstellenmememplerimplementierung übereinstimmen. Wenn ein Schnittstellenfunktionselement über ein Parameterarray verfügt, ist der entsprechende Parameter einer zugeordneten expliziten Schnittstellenelementimplementierung zulässig, aber nicht erforderlich, um den params Modifizierer zu haben. Wenn das Schnittstellenfunktionsmitglied kein Parameterarray besitzt, darf eine zugeordnete explizite Schnittstellenmitgliedimplementierung kein Parameterarray aufweisen.
Beispiel: In der folgenden Klasse
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }führt die Deklaration von
IComparable.CompareTodaher zu einem Kompilierungsfehler, daIComparablenicht in der Basisklassenliste vonShapeenthalten und auch keine Basisschnittstelle vonICloneableist. Ebenso in den Deklarationenclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }Die Deklaration von
ICloneable.CloneinEllipseführt zu einem Kompilierfehler, weilICloneablenicht explizit in der Basisklassenliste vonEllipseaufgeführt ist.Endbeispiel
Der Name des qualifizierten Schnittstellenmitglieds einer expliziten Schnittstellenmitgliedsimplementierung verweist auf die Schnittstelle, in der das Mitglied deklariert wurde.
Beispiel: In den folgenden Deklarationen
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }Die explizite Schnittstellenelementimplementierung von Paint muss als
IControl.Paintgeschrieben werden, nichtITextBox.Paint.Endbeispiel
19.6.3 Eindeutigkeit implementierter Schnittstellen
Die von einer generischen Typdeklaration implementierten Schnittstellen müssen für alle möglichen konstruierten Typen eindeutig bleiben. Ohne diese Regel wäre es unmöglich, die richtige Methode zum Aufrufen bestimmter konstruierter Typen zu bestimmen.
Beispiel: Angenommen, eine generische Klassendeklaration darf wie folgt geschrieben werden:
interface I<T> { void F(); } class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict { void I<U>.F() {...} void I<V>.F() {...} }Wenn dies zulässig wäre, wäre es unmöglich zu bestimmen, welcher Code im folgenden Fall ausgeführt werden soll:
I<int> x = new X<int, int>(); x.F();Endbeispiel
Um festzustellen, ob die Schnittstellenliste einer generischen Typdeklaration gültig ist, werden die folgenden Schritte ausgeführt:
- Lassen Sie uns
Ldie Liste der Schnittstellen sein, die direkt in einer generischen Klasse, Struktur oder SchnittstellendeklarationCangegeben sind. - Fügen Sie
Lalle Basisschnittstellen der Schnittstellen hinzu, die bereits inLvorhanden sind. - Entfernen Sie alle Duplikate aus
L. - Wenn ein möglicher abgeleiteter Typ aus
C, nach dem Einsetzen der Typargumente inL, dazu führt, dass zwei Schnittstellen inLidentisch sind, dann ist die Deklaration vonCungültig. Einschränkungsdeklarationen werden beim Ermitteln aller möglichen konstruierten Typen nicht berücksichtigt.
Hinweis: In der obigen Klassendeklaration
Xbesteht die SchnittstellenlisteLausl<U>undI<V>. Die Deklaration ist ungültig, da jeder konstruierte Typ, bei demUundVvom selben Typ sind, dazu führen würde, dass diese beiden Schnittstellen identische Typen sind. Hinweisende
Es ist möglich, schnittstellen, die auf unterschiedlichen Vererbungsebenen angegeben sind, zu vereinheitlichen:
interface I<T>
{
void F();
}
class Base<U> : I<U>
{
void I<U>.F() {...}
}
class Derived<U, V> : Base<U>, I<V> // Ok
{
void I<V>.F() {...}
}
Dieser Code ist gültig, auch wenn er sowohl Derived<U,V> als auch I<U> implementiert. Der Code
I<int> x = new Derived<int, int>();
x.F();
ruft die Methode in Derivedauf, da Derived<int,int>' sie effektiv neu implementiert I<int> wird (§19.6.7).
19.6.4 Implementierung generischer Methoden
Wenn eine generische Methode implizit eine Schnittstellenmethode implementiert, müssen die für jeden Methodentypparameter angegebenen Einschränkungen in beiden Deklarationen gleich sein (nachdem alle Schnittstellentypparameter durch die entsprechenden Typargumente ersetzt wurden), wobei Methodentypparameter durch Ordnungspositionen von links nach rechts identifiziert werden.
Beispiel: Im folgenden Code:
interface I<X, Y, Z> { void F<T>(T t) where T : X; void G<T>(T t) where T : Y; void H<T>(T t) where T : Z; } class C : I<object, C, string> { public void F<T>(T t) {...} // Ok public void G<T>(T t) where T : C {...} // Ok public void H<T>(T t) where T : string {...} // Error }die Methode
C.F<T>implizit implementiertI<object,C,string>.F<T>. In diesem Fall ist es nicht erforderlich (oder zulässig), dassC.F<T>die EinschränkungT: objectangibt, daobjecteine implizite Einschränkung für alle Typparameter ist. Die MethodeC.G<T>implementiertI<object,C,string>.G<T>implizit, da die Einschränkungen denen in der Schnittstelle entsprechen, nachdem die Schnittstellentypparameter durch die entsprechenden Typargumente ersetzt wurden. Die Einschränkung für die MethodeC.H<T>ist ein Fehler, da versiegelte Typen (stringin diesem Fall) nicht als Einschränkungen verwendet werden können. Das Weglassen der Einschränkung wäre auch ein Fehler, da Einschränkungen impliziter Schnittstellenmethodenimplementierungen übereinstimmen müssen. Daher ist es unmöglich, implizit zu implementierenI<object,C,string>.H<T>. Diese Schnittstellenmethode kann nur mithilfe einer expliziten Schnittstellenelementimplementierung implementiert werden:class C : I<object, C, string> { ... public void H<U>(U u) where U : class {...} void I<object, C, string>.H<T>(T t) { string s = t; // Ok H<T>(t); } }In diesem Fall ruft die explizite Schnittstellenmitgliedsimplementierung eine öffentliche Methode mit streng schwächeren Einschränkungen auf. Die Zuordnung von t zu s ist gültig, da
Teine Einschränkung vonT: stringerbt, auch wenn diese Einschränkung im Quellcode nicht ausgedrückt werden kann. Endbeispiel
Hinweis: Wenn eine generische Methode explizit eine Schnittstellenmethode implementiert, sind für die Implementierungsmethode keine Einschränkungen zulässig (§15.7.1, §19.6.2). Hinweisende
19.6.5 Schnittstellenzuordnung
Eine Klasse oder Struktur stellt Implementierungen aller abstrakten Member der Schnittstellen bereit, die in der Basisklassenliste der Klasse oder Struktur aufgeführt sind. Der Prozess zum Auffinden von Implementierungen von Schnittstellenmitgliedern in einer implementierenden Klasse oder Struktur wird als Schnittstellenzuordnung bezeichnet.
Die Schnittstellenzuordnung für eine Klasse oder Struktur C ermittelt eine Implementierung für jedes Mitglied jeder Schnittstelle, die in der Basis-Klassenliste von C angegeben ist. Die Implementierung eines bestimmten Schnittstellenelements I.M, wobei I es sich um die Schnittstelle handelt, in der das Element M deklariert wird, wird bestimmt, indem jede Klasse, Schnittstelle oder Struktur Suntersucht wird, beginnend mit C und wiederholt für jede aufeinander folgende Basisklasse und implementierte Schnittstelle von C, bis eine Übereinstimmung gefunden wird:
- Wenn
Seine Deklaration einer expliziten Implementierung eines Schnittstellenmitglieds enthält, die mitIundMübereinstimmt, dann ist dieses Mitglied die Implementierung vonI.M. - Andernfalls, wenn
Seine Deklaration eines nicht statischen öffentlichen Mitglieds enthält, das mitMübereinstimmt, dann ist dieses Mitglied die Implementierung vonI.M. Wenn mehrere Member übereinstimmen, ist nicht spezifiziert, welches Member die Implementierung vonI.Mist. Diese Situation kann nur auftreten, wennSes sich um einen konstruierten Typ handelt, bei dem die beiden Elemente, wie im generischen Typ deklariert, unterschiedliche Signaturen aufweisen, aber die Typargumente machen ihre Signaturen identisch.
Ein Kompilierungsfehler tritt auf, wenn für alle Member aller Schnittstellen keine Implementierungen gefunden werden können, die in der Basisklassenliste von C spezifiziert sind. Die Mitglieder einer Schnittstelle umfassen auch diejenigen, die von Basisschnittstellen geerbt werden.
Bei Membern eines konstruierten Schnittstellentyps wird davon ausgegangen, dass alle Typparameter durch die entsprechenden Typargumente gemäß §15.3.3 ersetzt werden.
Beispiel: Beispiel: Bei der generischen Schnittstellendeklaration:
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }die konstruierte Schnittstelle
I<string[]>hat die Elemente:string[] F(int x, string[,][] y); string[] this[int y] { get; }Endbeispiel
Für Die Zwecke der Schnittstellenzuordnung stimmt eine Klasse, schnittstelle oder struktur member A mit einem Schnittstellenmemem überein B , wenn:
-
AundBsind Methoden und die Namen-, Typ- und Parameterlisten vonAundBsind identisch. -
AundBsind Eigenschaften, der Name und der Typ vonAundBsind identisch, undAhat dieselben Zugriffs-Methoden wieB(Adarf zusätzliche Zugriffs-Methoden haben, wenn es sich nicht um eine explizite Schnittstellenmitgliedimplementierung handelt). -
AundBsind Ereignisse, und der Name und Typ vonAundBsind identisch. -
AundBsind Indexer, die Typ- und Parameterlisten vonAundBsind identisch, undAweist dieselben Accessoren auf wieB(Adarf zusätzliche Accessoren haben, wenn es sich nicht um eine explizite Schnittstellenmitgliedsimplementierung handelt).
Wichtige Auswirkungen des Schnittstellenzuordnungsalgorithmus sind:
- Explizite Schnittstellenmitgliedimplementierungen haben Vorrang gegenüber anderen Mitgliedern in derselben Klasse oder Struktur bei der Bestimmung des Klassen- oder Strukturmitglieds, das ein Schnittstellenmitglied implementiert.
- Weder nicht öffentliche noch statische Member sind an der Schnittstellenzuordnung beteiligt.
Beispiel: Im folgenden Code
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }Das
ICloneable.Clone-Mitglied vonCwird zur Implementierung vonCloneinICloneable, da explizite Implementierungen von Schnittstellenmenbern Vorrang vor anderen Membern haben.Endbeispiel
Wenn eine Klasse oder Struktur zwei oder mehr Schnittstellen implementiert, die ein Element mit demselben Namen, Typ und Parametertypen enthalten, ist es möglich, jede dieser Schnittstellenmember einem einzelnen Klassen- oder Strukturmember zuzuordnen.
Beispiel:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }Hier werden die
Paint-Methoden von sowohlIControlals auchIFormderPaint-Methode inPagezugeordnet. Es ist natürlich auch möglich, separate explizite Schnittstellenmitgliedimplementierungen für die beiden Methoden zu haben.Endbeispiel
Wenn eine Klasse oder Struktur eine Schnittstelle implementiert, die ausgeblendete Member enthält, müssen einige Member möglicherweise über explizite Schnittstellenmemberimplementierungen implementiert werden.
Beispiel:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }Eine Implementierung dieser Schnittstelle erfordert mindestens eine explizite Schnittstellenmemmimplementierung und würde eine der folgenden Formen annehmen:
class C1 : IDerived { int IBase.P { get; } int IDerived.P() {...} } class C2 : IDerived { public int P { get; } int IDerived.P() {...} } class C3 : IDerived { int IBase.P { get; } public int P() {...} }Endbeispiel
Wenn eine Klasse mehrere Schnittstellen implementiert, die dieselbe Basisschnittstelle aufweisen, kann es nur eine Implementierung der Basisschnittstelle geben.
Beispiel: Im folgenden Code
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } class ComboBox : IControl, ITextBox, IListBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} void IListBox.SetItems(string[] items) {...} }Es ist nicht möglich, separate Implementierungen für die in der Basisklassenliste benannte
IControl, die vonIControlgeerbteITextBoxund die vonIControlgeerbteIListBoxzu haben. Tatsächlich gibt es keinen Begriff einer separaten Identität für diese Schnittstellen. Stattdessen teilen sich die Implementierungen vonITextBoxundIListBoxdie gleiche Implementierung vonIControl, undComboBoxwird einfach als Implementierung von drei Schnittstellen betrachtet:IControl,ITextBoxundIListBox.Endbeispiel
Die Mitglieder einer Basisklasse nehmen an der Schnittstellenzuordnung teil.
Beispiel: Im folgenden Code
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }Die Methode
FinClass1wird bei der Implementierung vonClass2'svonInterface1verwendet.Endbeispiel
19.6.6 Schnittstellenimplementierungsvererbung
Eine Klasse erbt alle Schnittstellenimplementierungen, die von ihren Basisklassen bereitgestellt werden.
Ohne explizite erneute Implementierung einer Schnittstelle kann eine abgeleitete Klasse die schnittstellenzuordnungen, die sie von ihren Basisklassen erbt, nicht ändern.
Beispiel: In den Deklarationen
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }Die
PaintMethode inTextBoxblendet diePaintMethode inControlaus, ändert jedoch nicht die Zuordnung vonControl.PaintzuIControl.Paint, und Aufrufe vonPaintdurch Klasseninstanzen und Schnittstelleninstanzen haben die folgenden Auswirkungen:Control c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes Control.Paint();Endbeispiel
Wenn jedoch eine Schnittstellenmethode einer virtuellen Methode in einer Klasse zugeordnet wird, ist es möglich, abgeleitete Klassen die virtuelle Methode außer Kraft zu setzen und die Implementierung der Schnittstelle zu ändern.
Beispiel: Umschreiben der obigen Deklarationen in
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }die folgenden Effekte werden nun beobachtet
Control c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes TextBox.Paint();Endbeispiel
Da explizite Schnittstellenmemberimplementierungen nicht virtuell deklariert werden können, ist es nicht möglich, eine explizite Schnittstellenmemberimplementierung zu überschreiben. Es ist jedoch vollkommen zulässig, dass eine explizite Schnittstellenimplementierung eine andere Methode aufruft, und diese andere Methode kann virtuell deklariert werden, damit abgeleitete Klassen sie überschreiben können.
Beispiel:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }Hier können von
Controlabgeleitete Klassen die Implementierung vonIControl.Paintdurch Überschreiben derPaintControl-Methode spezialisieren.Endbeispiel
19.6.7 Schnittstellenumsetzung
Eine Klasse, die eine Schnittstellenimplementierung erbt , kann die Schnittstelle erneut implementieren , indem sie in die Basisklassenliste eingeschlossen wird.
Eine erneute Implementierung einer Schnittstelle folgt genau den gleichen Schnittstellenzuordnungsregeln wie eine anfängliche Implementierung einer Schnittstelle. Somit hat die geerbte Schnittstellenzuordnung keinerlei Auswirkungen auf die Schnittstellenzuordnung, die für die erneute Implementierung der Schnittstelle eingerichtet wurde.
Beispiel: In den Deklarationen
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }die Tatsache, dass
ControlIControl.PaintaufControl.IControl.Paintabbildet, wirkt sich nicht auf die Neuimplementierung inMyControlaus, dieIControl.PaintaufMyControl.Paintabbildet.Endbeispiel
Geerbte Deklarationen öffentlicher Member und geerbte explizite Schnittstellenmemberdeklarationen sind am Schnittstellenzuordnungsprozess für neu implementierte Schnittstellen beteiligt.
Beispiel:
interface IMethods { void F(); void G(); void H(); void I(); } class Base : IMethods { void IMethods.F() {} void IMethods.G() {} public void H() {} public void I() {} } class Derived : Base, IMethods { public void F() {} void IMethods.H() {} }Hier wird die Implementierung von
IMethodsinDeriveddazu verwendet, die Schnittstellenmethoden aufDerived.F,Base.IMethods.G,Derived.IMethods.HundBase.Iabzubilden.Endbeispiel
Wenn eine Klasse eine Schnittstelle implementiert, implementiert sie implizit auch alle Basisschnittstellen dieser Schnittstelle. Ebenso ist eine erneute Implementierung einer Schnittstelle implizit eine erneute Implementierung aller Basisschnittstellen der Schnittstelle.
Beispiel:
interface IBase { void F(); } interface IDerived : IBase { void G(); } class C : IDerived { void IBase.F() {...} void IDerived.G() {...} } class D : C, IDerived { public void F() {...} public void G() {...} }Hier wird durch die erneute Implementierung von
IDerivedauchIBaseneu implementiert undIBase.FzuD.Fzugeordnet.Endbeispiel
19.6.8 Abstrakte Klassen und Schnittstellen
Wie eine nicht abstrakte Klasse muss eine abstrakte Klasse Implementierungen aller abstrakten Member der Schnittstellen bereitstellen, die in der Basisklassenliste der Klasse aufgeführt sind. Eine abstrakte Klasse ist jedoch berechtigt, Schnittstellenmethoden abstrakten Methoden zuzuordnen.
Beispiel:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }Hier ordnet die Implementierung von
IMethodsdieFundGabstrakten Methoden zu, die in nicht-abstrakten Klassen, die vonCabgeleitet werden, überschrieben werden sollen.Endbeispiel
Explizite Schnittstellenmememimplementierungen können nicht abstrahiert werden, aber explizite Schnittstellenmememimplementierungen dürfen natürlich abstrakte Methoden aufrufen.
Beispiel:
interface IMethods { void F(); void G(); } abstract class C: IMethods { void IMethods.F() { FF(); } void IMethods.G() { GG(); } protected abstract void FF(); protected abstract void GG(); }Hier müssen nicht-abstrakte Klassen, die von
Cabgeleitet sind,FFundGGüberschreiben und damit die tatsächliche Implementierung vonIMethodsbereitstellen.Endbeispiel
ECMA C# draft specification