18 Interfaces
18.1 Algemeen
Een interface definieert een contract. Een klasse of struct die een interface implementeert, voldoet aan het contract. Een interface kan overnemen van meerdere basisinterfaces en een klasse of struct kan meerdere interfaces implementeren.
Interfaces kunnen methoden, eigenschappen, gebeurtenissen en indexeerfuncties bevatten. De interface zelf biedt geen implementaties voor de leden die deze declareert. De interface geeft alleen de leden op die moeten worden geleverd door klassen of structs die de interface implementeren.
18.2 Interfacedeclaraties
18.2.1 Algemeen
Een interface_declaration is een type_declaration (§14.7) die een nieuw interfacetype declareert.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
Een interface_declaration bestaat uit een optionele set kenmerken (§22), gevolgd door een optionele set interface_modifiers (§18.2.2), gevolgd door een optionele gedeeltelijke wijziging (§15.2.7), gevolgd door het trefwoord interface
en een id die de interface een naam geeft, gevolgd door een optionele variant_type_parameter_list specificatie (§18.2.3), gevolgd door een optioneel interface_base specificatie (§18.2.4), gevolgd door een optionele specificatie van type_parameter_constraints_clause(§15.2.5), gevolgd door een interface_body (§18.3), eventueel gevolgd door een puntkomma.
Een interfacedeclaratie levert geen type_parameter_constraints_clauses, tenzij zij ook een variant_type_parameter_list levert.
Een interfacedeclaratie die een variant_type_parameter_list levert, is een algemene interfacedeclaratie. Bovendien is elke interface die is genest in een algemene klassedeclaratie of een algemene structdeclaratie zelf een algemene interfacedeclaratie, aangezien typeargumenten voor het betreffende type worden opgegeven om een samengesteld type te maken (§8.4).
18.2.2 Interface modifiers
Een interface_declaration kan eventueel een reeks interfaceaanpassingen bevatten:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).
Het is een compilatiefout voor dezelfde wijziging die meerdere keren wordt weergegeven in een interfacedeclaratie.
De new
wijzigingsfunctie is alleen toegestaan op interfaces die binnen een klasse zijn gedefinieerd. Hiermee wordt aangegeven dat de interface een overgenomen lid met dezelfde naam verbergt, zoals beschreven in §15.3.5.
De public
, protected
en internal
private
modifiers bepalen de toegankelijkheid van de interface. Afhankelijk van de context waarin de interfacedeclaratie plaatsvindt, zijn mogelijk slechts enkele van deze modifiers toegestaan (§7.5.2). Wanneer een gedeeltelijke typedeclaratie (§15.2.7) een toegankelijkheidsspecificatie (via de public
, protected
, internal
en private
modifiers) bevat, zijn de regels in §15.2.2 van toepassing.
18.2.3 Parameterlijsten voor varianttypen
18.2.3.1 Algemeen
Parameterlijsten voor varianttypen kunnen alleen worden uitgevoerd op interface- en gedelegeerdentypen. Het verschil met gewone type_parameter_lists is de optionele variance_annotation voor elke typeparameter.
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' attributes? variance_annotation?
type_parameter
;
variance_annotation
: 'in'
| 'out'
;
Als de afwijkingsaantekening isout
, wordt de typeparameter covariant genoemd. Als de afwijkingsaantekening isin
, wordt de typeparameter als contravariant beschouwd. Als er geen afwijkingsaantekening is, wordt de typeparameter als invariant gezegd.
Voorbeeld: In het volgende:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
is covariant,Y
is contravariant enZ
is invariant.eindvoorbeeld
Als een algemene interface in meerdere delen wordt gedeclareerd (§15.2.3), geeft elke gedeeltelijke aangifte dezelfde variantie op voor elke typeparameter.
18.2.3.2 Variantieveiligheid
Het optreden van afwijkingsannotaties in de lijst met typeparameters van een type beperkt de plaatsen waar typen kunnen optreden in de typedeclaratie.
Een type T is uitvoer onveilig als een van de volgende bewaringen geldt:
-
T
is een parameter voor het type contravariant -
T
is een matrixtype met een uitvoer onveilig elementtype -
T
is een interface of gemachtigde typeSᵢ,... Aₑ
dat is samengesteld uit een algemeen typeS<Xᵢ, ... Xₑ>
, waarbij ten minste éénAᵢ
van de volgende bewaringen geldt:-
Xᵢ
is covariant of invariant enAᵢ
is uitvoer onveilig. -
Xᵢ
is contravariant of invariant enAᵢ
is onveilig voor invoer.
-
Een type T is invoer onveilig als een van de volgende bewaringen geldt:
-
T
is een parameter voor het covarianttype -
T
is een matrixtype met een invoer onveilig elementtype -
T
is een interface of gemachtigde typeS<Aᵢ,... Aₑ>
dat is samengesteld uit een algemeen typeS<Xᵢ, ... Xₑ>
, waarbij ten minste éénAᵢ
van de volgende bewaringen geldt:-
Xᵢ
is covariant of invariant enAᵢ
is onveilig voor invoer. -
Xᵢ
is contravariant of invariant enAᵢ
is uitvoer onveilig.
-
Intuïtief is een uitvoer onveilig type verboden in een uitvoerpositie en is een invoer onveilig type niet toegestaan in een invoerpositie.
Een type is uitvoerveilig als het niet uitvoer onveilig is en invoerveilig als dit niet onveilig is.
18.2.3.3 Variantieconversie
Het doel van afwijkingsaantekeningen is om meer lenige (maar toch typeveilige) conversies naar interface- en gedelegeerdentypen te bieden. Daartoe maken de definities van impliciete (§10.2) en expliciete conversies (§10.3) gebruik van het begrip variantie-conversie, die als volgt wordt gedefinieerd:
Een type T<Aᵢ, ..., Aᵥ>
is variantie-converteerbaar naar een type T<Bᵢ, ..., Bᵥ>
als T
het een interface of een gemachtigde type is dat is gedeclareerd met de varianttypeparameters T<Xᵢ, ..., Xᵥ>
, en voor elke varianttypeparameter Xᵢ
een van de volgende bewaringen:
-
Xᵢ
is covariant en er bestaat een impliciete verwijzing of identiteitsconversie vanAᵢ
naarBᵢ
-
Xᵢ
is contravariant en er bestaat een impliciete verwijzing of identiteitsconversie vanBᵢ
naarAᵢ
-
Xᵢ
is invariant en er bestaat een identiteitsconversie vanAᵢ
naarBᵢ
18.2.4 Basisinterfaces
Een interface kan overnemen van nul of meer interfacetypen, die de expliciete basisinterfaces van de interface worden genoemd. Wanneer een interface een of meer expliciete basisinterfaces heeft, wordt de interface-id gevolgd door een dubbele punt en een door komma's gescheiden lijst met basisinterfacetypen.
interface_base
: ':' interface_type_list
;
De expliciete basisinterfaces kunnen worden samengesteld uit interfacetypen (§8.4, §18.2). Een basisinterface kan geen eigen typeparameter zijn, maar kan wel betrekking hebben op de typeparameters die binnen het bereik vallen.
Voor een samengesteld interfacetype worden de expliciete basisinterfaces gevormd door de expliciete basisinterfacedeclaraties op de algemene typedeclaratie te nemen en voor elke type_parameter in de basisinterfacedeclaratie de bijbehorende type_argument van het samengestelde type te vervangen.
De expliciete basisinterfaces van een interface moeten minstens zo toegankelijk zijn als de interface zelf (§7.5.5).
Opmerking: het is bijvoorbeeld een compilatiefout om een
private
ofinternal
interface op te geven in de interface_base van eenpublic
interface. eindnotitie
Het is een compilatiefout voor een interface om direct of indirect van zichzelf over te nemen.
De basisinterfaces van een interface zijn de expliciete basisinterfaces en de bijbehorende basisinterfaces. Met andere woorden, de set basisinterfaces is de volledige transitieve sluiting van de expliciete basisinterfaces, hun expliciete basisinterfaces, enzovoort. Een interface neemt alle leden van de basisinterfaces over.
Voorbeeld: In de volgende code
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
de basisinterfaces
IComboBox
zijnIControl
,ITextBox
enIListBox
. Met andere woorden, deIComboBox
bovenstaande interface neemt ledenSetText
enSetItems
ookPaint
over.eindvoorbeeld
Leden die zijn overgenomen van een samengesteld algemeen type, worden overgenomen na het vervangen van het type. Dat wil gezegd: alle samenstellende typen in het lid hebben de typeparameters van de basisklassedeclaratie vervangen door de bijbehorende typeargumenten die worden gebruikt in de specificatie van de class_base .
Voorbeeld: In de volgende code
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
de interface
IDerived
neemt deCombine
methode over nadat de typeparameterT
is vervangen doorstring[,]
.eindvoorbeeld
Een klasse of struct waarmee een interface wordt geïmplementeerd, implementeert ook impliciet alle basisinterfaces van de interface.
De verwerking van interfaces op meerdere delen van een gedeeltelijke interfacedeclaratie (§15.2.7) wordt verder besproken in §15.2.4.3.
Elke basisinterface van een interface is uitvoerveilig (§18.2.3.2).
18.3 Interfacebody
De interface_body van een interface definieert de leden van de interface.
interface_body
: '{' interface_member_declaration* '}'
;
18.4 Interfaceleden
18.4.1 Algemeen
De leden van een interface zijn de leden die zijn overgenomen van de basisinterfaces en de leden die door de interface zelf zijn gedeclareerd.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
Een interfacedeclaratie declareert nul of meer leden. De leden van een interface zijn methoden, eigenschappen, gebeurtenissen of indexeerfuncties. Een interface kan geen constanten, velden, operators, instantieconstructors, finalizers of typen bevatten, noch kan een interface statische leden van welke aard dan ook bevatten.
Alle interfaceleden hebben impliciet openbare toegang. Het is een compilatiefout voor declaraties van interfaceleden om eventuele modifiers op te nemen.
Een interface_declaration maakt een nieuwe declaratieruimte (§7.3) en de typeparameters en interface_member_declarationdie direct zijn opgenomen in de interface_declaration nieuwe leden in deze declaratieruimte introduceren. De volgende regels zijn van toepassing op interface_member_declaration s:
- De naam van een typeparameter in de variant_type_parameter_list van een interfacedeclaratie verschilt van de namen van alle andere typeparameters in hetzelfde variant_type_parameter_list en verschilt van de namen van alle leden van de interface.
- De naam van een methode verschilt van de namen van alle eigenschappen en gebeurtenissen die in dezelfde interface zijn gedeclareerd. Bovendien verschilt de handtekening (§7.6) van een methode van de handtekeningen van alle andere methoden die in dezelfde interface zijn aangegeven, en twee methoden die in dezelfde interface zijn gedeclareerd, mogen geen handtekeningen hebben die uitsluitend verschillen van
in
,out
enref
. - De naam van een eigenschap of gebeurtenis verschilt van de namen van alle andere leden die in dezelfde interface zijn aangegeven.
- De handtekening van een indexeerfunctie verschilt van de handtekeningen van alle andere indexeerfuncties die in dezelfde interface zijn aangegeven.
De overgenomen leden van een interface maken specifiek geen deel uit van de declaratieruimte van de interface. Een interface mag dus een lid met dezelfde naam of handtekening declareren als een overgenomen lid. Wanneer dit gebeurt, wordt het afgeleide interfacelid gezegd om het lid van de basisinterface te verbergen . Het verbergen van een overgenomen lid wordt niet beschouwd als een fout, maar zorgt ervoor dat een compiler een waarschuwing geeft. Om de waarschuwing te onderdrukken, moet de verklaring van het lid van de afgeleide interface een new
wijzigingsfunctie bevatten om aan te geven dat het afgeleide lid bedoeld is om het basislid te verbergen. Dit onderwerp wordt verder besproken in §7.7.2.3.
Als een new
wijzigingsfunctie is opgenomen in een declaratie die een overgenomen lid niet verbergt, wordt er een waarschuwing voor dat effect gegeven. Deze waarschuwing wordt onderdrukt door de new
wijzigingsfunctie te verwijderen.
Opmerking: De leden in de klas
object
zijn niet strikt genomen leden van een interface (§18.4). De leden in de klasobject
zijn echter beschikbaar via het opzoeken van leden in elk interfacetype (§12.5). eindnotitie
De set leden van een interface die in meerdere delen is gedeclareerd (§15.2.7) is de vereniging van de leden die in elk deel zijn gedeclareerd. De organen van alle delen van de interfacedeclaratie delen dezelfde declaratieruimte (§7.3) en het bereik van elk lid (§7.7) strekt zich uit tot de lichamen van alle onderdelen.
18.4.2 Interfacemethoden
Interfacemethoden worden gedeclareerd met behulp van interface_method_declaration s:
interface_method_declaration
: attributes? 'new'? return_type interface_method_header
| attributes? 'new'? ref_kind ref_return_type interface_method_header
;
interface_method_header
: identifier '(' parameter_list? ')' ';'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
De kenmerken, return_type, ref_return_type, id en parameter_list van een interfacemethodedeclaratie hebben dezelfde betekenis als die van een methodedeclaratie in een klasse (§15.6). Een declaratie van een interfacemethode is niet toegestaan om een methodetekst op te geven en de declaratie eindigt daarom altijd met een puntkomma.
Alle parametertypen van een interfacemethode zijn invoerveilig (§18.2.3.2) en het retourtype moet veilig void
zijn of worden uitgevoerd. Bovendien zijn alle typen uitvoer- of referentieparameters ook uitvoerveilig.
Opmerking: uitvoerparameters moeten invoerveilig zijn vanwege algemene implementatiebeperkingen. eindnotitie
Bovendien zijn elke beperking van het klassetype, de beperking van het interfacetype en de parameterbeperking van het type voor elk type van de methode invoerveilig.
Bovendien zijn elke beperking van het klassetype, de beperking van het interfacetype en de parameterbeperking van het type voor elk type parameter van de methode invoerveilig.
Deze regels zorgen ervoor dat elk covariant- of contravariantgebruik van de interface typesafe blijft.
Voorbeeld:
interface I<out T> { void M<U>() where U : T; // Error }
is ongeldig omdat het gebruik van
T
als parameterbeperkingU
voor een type niet invoerveilig is.Als deze beperking niet is ingesteld, zou het mogelijk zijn om de veiligheid van het type op de volgende manier te schenden:
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
Dit is eigenlijk een aanroep naar
C.M<E>
. Maar die aanroep vereist datE
afgeleid is vanD
, zodat typeveiligheid hier wordt geschonden.eindvoorbeeld
18.4.3 Interface-eigenschappen
Interface-eigenschappen worden gedeclareerd met interface_property_declaration s:
interface_property_declaration
: attributes? 'new'? type identifier '{' interface_accessors '}'
| attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
;
interface_accessors
: attributes? 'get' ';'
| attributes? 'set' ';'
| attributes? 'get' ';' attributes? 'set' ';'
| attributes? 'set' ';' attributes? 'get' ';'
;
ref_interface_accessor
: attributes? 'get' ';'
;
De kenmerken, het type en de id van een declaratie van interface-eigenschappen hebben dezelfde betekenis als die van een eigenschapsdeclaratie in een klasse (§15.7).
De toegangsrechten van een declaratie van een interface-eigenschap komen overeen met de toegangsrechten van een klasse-eigenschapsdeclaratie (§15.7.3), behalve dat de accessor_body altijd een puntkomma is. De accessors geven dus aan of de eigenschap alleen-lezen, alleen-lezen of alleen-schrijven is.
Het type interface-eigenschap moet uitvoerveilig zijn als er een get accessor is en moet invoerveilig zijn als er een set accessor is.
18.4.4 Interface-gebeurtenissen
Interface-gebeurtenissen worden gedeclareerd met behulp van interface_event_declaration s:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
De kenmerken, het type en de id van een declaratie van interface-gebeurtenissen hebben dezelfde betekenis als die van een gebeurtenisdeclaratie in een klasse (§15.8).
Het type interfacegebeurtenis moet invoerveilig zijn.
18.4.5 Interface-indexeerfuncties
Interface-indexeerfuncties worden gedeclareerd met behulp van interface_indexer_declaration s:
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' parameter_list ']'
'{' interface_accessors '}'
| attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
'{' ref_interface_accessor '}'
;
De kenmerken, het type en parameter_list van een interface-indexeerfunctiedeclaratie hebben dezelfde betekenis als die van een indexeerfunctiedeclaratie in een klasse (§15.9).
De toegangsrechten van een declaratie van een interface-indexeerfunctie komen overeen met de toegangsrechten van een klasse-indexeerfunctie (§15.9), behalve dat de accessor_body altijd een puntkomma is. De accessors geven dus aan of de indexeerfunctie alleen-lezen, alleen-lezen of alleen-schrijven is.
Alle parametertypen van een interface-indexeerfunctie moeten invoerveilig zijn (§18.2.3.2). Bovendien zijn alle typen uitvoer- of referentieparameters ook uitvoerveilig.
Opmerking: uitvoerparameters moeten invoerveilig zijn vanwege algemene implementatiebeperkingen. eindnotitie
Het type interface-indexeerfunctie moet uitvoerveilig zijn als er een get accessor is en moet invoerveilig zijn als er een set accessor is.
18.4.6 Interfacelidtoegang
Interfaceleden worden benaderd via toegang tot leden (§12.8.7) en indexeerfunctietoegang (§12.8.12.3) expressies in de vorm van I.M
en I[A]
, waarbij I
een interfacetype is, M
een methode, eigenschap of gebeurtenis van dat interfacetype is en A
een lijst met indexeerargumenten is.
Voor interfaces die strikt enkelvoudige overname zijn (elke interface in de overnameketen heeft precies nul of één directe basisinterface), zijn de effecten van de ledenopzoeking (§12.5), methode-aanroep (§12.8.10.2) en indexeerfunctietoegang (§12.8.12.3) precies hetzelfde als voor klassen en structs: Meer afgeleide leden verbergen minder afgeleide leden met dezelfde naam of signatuur. Voor interfaces met meerdere overnames kunnen er echter dubbelzinnigheden optreden wanneer twee of meer niet-gerelateerde basisinterfaces leden declareren met dezelfde naam of handtekening. Deze subclause toont verschillende voorbeelden, waarvan sommige leiden tot dubbelzinnigheid en andere die niet. In alle gevallen kunnen expliciete casts worden gebruikt om de ambiguïteiten op te lossen.
Voorbeeld: In de volgende code
interface IList { int Count { get; set; } } interface ICounter { void Count(int i); } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count(1); // Error x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count(1); // Ok, invokes ICounter.Count } }
de eerste twee instructies leiden tot compilatiefouten omdat het opzoeken van leden (§12.5)
Count
IListCounter
dubbelzinnig is. Zoals geïllustreerd in het voorbeeld, wordt de dubbelzinnigheid opgelost door naar het juiste type basisinterface te castenx
. Dergelijke casts hebben geen runtimekosten. Ze bestaan alleen uit het weergeven van het exemplaar als een minder afgeleid type tijdens het compileren.eindvoorbeeld
Voorbeeld: In de volgende 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 } }
de aanroep
n.Add(1)
selecteertIInteger.Add
door overbelastingsregels van §12.6.4 toe te passen. Op dezelfde manier selecteertn.Add(1.0)
de aanroepIDouble.Add
. Wanneer expliciete casts worden ingevoegd, is er slechts één kandidaatmethode en dus geen dubbelzinnigheid.eindvoorbeeld
Voorbeeld: In de volgende 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 } }
het
IBase.F
lid is verborgen door hetILeft.F
lid. De aanroepd.F(1)
selecteert dus, ook alILeft.F
lijkt het niet verborgen te zijn in het toegangspadIBase.F
dat doorlooptIRight
.De intuïtieve regel voor het verbergen in interfaces voor meerdere overnames is dit: als een lid is verborgen in een toegangspad, wordt deze verborgen in alle toegangspaden. Omdat het toegangspad
IDerived
van naar verbergtILeft
IBase
, wordt het lid ook verborgen in het toegangspadIBase.F
IDerived
van naarIRight
IBase
.eindvoorbeeld
18.5 Gekwalificeerde interfacelidnamen
Een interfacelid wordt soms aangeduid met de naam van het gekwalificeerde interfacelid. De gekwalificeerde naam van een interfacelid bestaat uit de naam van de interface waarin het lid wordt gedeclareerd, gevolgd door een punt, gevolgd door de naam van het lid. De gekwalificeerde naam van een lid verwijst naar de interface waarin het lid wordt gedeclareerd.
Voorbeeld: Gegeven de declaraties
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
de gekwalificeerde naam
Paint
isIControl.Paint
en de gekwalificeerde naam van SetText isITextBox.SetText
. In het bovenstaande voorbeeld is het niet mogelijk om te verwijzen naarPaint
ITextBox.Paint
.eindvoorbeeld
Wanneer een interface deel uitmaakt van een naamruimte, kan een naam van een lid van de gekwalificeerde interface de naamruimtenaam bevatten.
Voorbeeld:
namespace System { public interface ICloneable { object Clone(); } }
Binnen de
System
naamruimte zijn beideICloneable.Clone
enSystem.ICloneable.Clone
gekwalificeerde interfacelidnamen voor deClone
methode.eindvoorbeeld
18.6 Interface-implementaties
18.6.1 Algemeen
Interfaces kunnen worden geïmplementeerd door klassen en structs. Om aan te geven dat een klasse of struct rechtstreeks een interface implementeert, wordt de interface opgenomen in de lijst met basisklassen van de klasse of struct.
Voorbeeld:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
eindvoorbeeld
Een klasse of struct die rechtstreeks een interface implementeert, implementeert ook impliciet alle basisinterfaces van de interface. Dit geldt zelfs als de klasse of struct niet expliciet alle basisinterfaces in de lijst met basisklassen weergeeft.
Voorbeeld:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
Hier implementeert de klasse
TextBox
zowelIControl
alsITextBox
.eindvoorbeeld
Wanneer een klasse C
rechtstreeks een interface implementeert, worden alle klassen die zijn afgeleid van C
de interface ook impliciet geïmplementeerd.
De basisinterfaces die zijn opgegeven in een klassedeclaratie kunnen worden samengesteld uit interfacetypen (§8.4, §18.2).
Voorbeeld: De volgende code illustreert hoe een klasse samengestelde interfacetypen kan implementeren:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
eindvoorbeeld
De basisinterfaces van een algemene klassedeclaratie voldoen aan de in §18.6.3 beschreven uniekheidsregel.
18.6.2 Expliciete implementaties van interfaceleden
Voor het implementeren van interfaces kan een klasse of struct expliciete implementaties van interfaceleden declareren. Een expliciete implementatie van interfaceleden is een methode, eigenschap, gebeurtenis of indexeerfunctiedeclaratie die verwijst naar de naam van een gekwalificeerde interfacelid.
Voorbeeld:
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
IDictionary<int,T>.this
enIDictionary<int,T>.Add
zijn expliciete implementaties van interfaceleden.eindvoorbeeld
Voorbeeld: In sommige gevallen is de naam van een interfacelid mogelijk niet geschikt voor de implementatieklasse. In dat geval kan het interfacelid worden geïmplementeerd met behulp van expliciete implementatie van interfaceleden. Een klasse die bijvoorbeeld een bestandsabstractie implementeert, zou waarschijnlijk een
Close
lidfunctie implementeren die het effect heeft van het vrijgeven van de bestandsresource en deDispose
methode van deIDisposable
interface implementeren met behulp van expliciete implementatie van interfaceleden: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); } }
eindvoorbeeld
Het is niet mogelijk om toegang te krijgen tot een expliciete implementatie van een interfacelid via de naam van het gekwalificeerde interfacelid in een methodeaanroep, eigenschapstoegang, gebeurtenistoegang of indexeertoegang. Een expliciete implementatie van interfaceleden kan alleen worden geopend via een interface-exemplaar en wordt in dat geval gewoon verwezen naar de naam van het lid.
Het is een compilatiefout voor een expliciete implementatie van interfaceleden om andere modifiers (§15.6) dan extern
of async
op te nemen.
Het is een compilatiefout voor een expliciete interfacemethode-implementatie om type_parameter_constraints_clauses op te nemen. De beperkingen voor een algemene implementatie van een expliciete interfacemethode worden overgenomen van de interfacemethode.
Opmerking: Expliciete implementaties van interfaceleden hebben verschillende toegankelijkheidskenmerken dan andere leden. Omdat expliciete implementaties van interfaceleden nooit toegankelijk zijn via een gekwalificeerde naam van een interfacelid in een methodeaanroep of toegang tot een eigenschap, zijn ze in zekere zin privé. Omdat ze echter toegankelijk zijn via de interface, zijn ze in zekere zin ook zo openbaar als de interface waarin ze worden gedeclareerd. Expliciete implementaties van interfaceleden dienen twee primaire doeleinden:
- Omdat expliciete implementaties van interfaceleden niet toegankelijk zijn via klasse- of struct-exemplaren, kunnen interface-implementaties worden uitgesloten van de openbare interface van een klasse of struct. Dit is met name handig wanneer een klasse of struct een interne interface implementeert die niet van belang is voor een consument van die klasse of struct.
- Expliciete implementaties van interfaceleden maken ondubbelzinnigheid mogelijk van interfaceleden met dezelfde handtekening. Zonder expliciete implementaties van interfaceleden zou het onmogelijk zijn voor een klasse of struct om verschillende implementaties van interfaceleden met dezelfde handtekening en retourtype te hebben, zoals het zou onmogelijk zijn voor een klasse of struct om een implementatie te hebben voor alle interfaceleden met dezelfde handtekening, maar met verschillende retourtypen.
eindnotitie
Voor een geldige implementatie van een expliciet interfacelid moet de klasse of struct een interface in de basisklasselijst noemen die een lid bevat waarvan de naam van het gekwalificeerde interfacelid, het type, het aantal parameters en parametertypen exact overeenkomen met die van de expliciete implementatie van interfaceleden. Als een lid van een interfacefunctie een parametermatrix heeft, is de bijbehorende parameter van een gekoppelde expliciete implementatie van interfaceleden toegestaan, maar niet vereist, om de params
wijzigingsfunctie te hebben. Als het lid van de interfacefunctie geen parametermatrix heeft, heeft een gekoppelde expliciete implementatie van interfaceleden geen parametermatrix.
Voorbeeld: Dus in de volgende klasse
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
de declaratie van
IComparable.CompareTo
resultaten in een compilatietijdfout omdatIComparable
deze niet wordt vermeld in de basisklasselijst vanShape
en geen basisinterface is vanICloneable
. Eveneens in de declaratiesclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
de declaratie van
ICloneable.Clone
in resultaten inEllipse
een compilatiefout omdatICloneable
deze niet expliciet wordt vermeld in de lijst met basisklassen vanEllipse
.eindvoorbeeld
De naam van het gekwalificeerde interfacelid van een expliciete implementatie van interfaceleden verwijst naar de interface waarin het lid is gedeclareerd.
Voorbeeld: Dus in de declaraties
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
de expliciete implementatie van het interfacelid van Paint moet worden geschreven als
IControl.Paint
, nietITextBox.Paint
.eindvoorbeeld
18.6.3 Uniekheid van geïmplementeerde interfaces
De interfaces die door een algemene typedeclaratie worden geïmplementeerd, blijven uniek voor alle mogelijke samengestelde typen. Zonder deze regel zou het onmogelijk zijn om de juiste methode te bepalen die moet worden aangeroepen voor bepaalde samengestelde typen.
Voorbeeld: Stel dat een algemene klassedeclaratie als volgt mag worden geschreven:
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() {...} }
Als dit is toegestaan, zou het onmogelijk zijn om te bepalen welke code in het volgende geval moet worden uitgevoerd:
I<int> x = new X<int, int>(); x.F();
eindvoorbeeld
Om te bepalen of de interfacelijst van een algemene typedeclaratie geldig is, worden de volgende stappen uitgevoerd:
- Laten we
L
de lijst met interfaces zijn die rechtstreeks zijn opgegeven in een algemene klasse, struct of interfacedeclaratieC
. - Toevoegen aan
L
alle basisinterfaces van de interfaces die al aanwezig zijnL
. - Verwijder eventuele duplicaten uit
L
. - Als een mogelijk samengesteld type dat is gemaakt
C
, zou, nadat typeargumenten worden vervangenL
, ervoor zorgen dat twee interfacesL
identiek zijn, dan is de declaratie ongeldigC
. Declaraties van beperkingen worden niet overwogen bij het bepalen van alle mogelijke samengestelde typen.
Opmerking: In de bovenstaande klassedeclaratie
X
bestaat de interfacelijstL
uitl<U>
enI<V>
. De declaratie is ongeldig omdat elk geconstrueerd type metU
hetzelfdeV
type ertoe zou leiden dat deze twee interfaces identieke typen zijn. eindnotitie
Het is mogelijk voor interfaces die zijn opgegeven op verschillende overnameniveaus om het volgende te samenvoegen:
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() {...}
}
Deze code is geldig, ook al Derived<U,V>
worden zowel als I<U>
I<V>
. De code
I<int> x = new Derived<int, int>();
x.F();
roept de methode aan in Derived
, aangezien Derived<int,int>'
deze effectief opnieuw wordt geïmplementeerd I<int>
(§18.6.7).
18.6.4 Implementatie van algemene methoden
Wanneer een algemene methode impliciet een interfacemethode implementeert, moeten de beperkingen voor elke parameter van het methodetype equivalent zijn in beide declaraties (nadat parameters van het interfacetype worden vervangen door de juiste typeargumenten), waarbij parameters van het methodetype worden geïdentificeerd door rangtelposities, van links naar rechts.
Voorbeeld: In de volgende 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 }
de methode
C.F<T>
impliciet implementeertI<object,C,string>.F<T>
. In dit gevalC.F<T>
is niet vereist (noch toegestaan) om de beperkingT: object
op te geven, omdatobject
dit een impliciete beperking is voor alle typeparameters. De methodeC.G<T>
implementeertI<object,C,string>.G<T>
impliciet omdat de beperkingen overeenkomen met die in de interface, nadat de parameters van het interfacetype zijn vervangen door de bijbehorende typeargumenten. De beperking voor de methodeC.H<T>
is een fout omdat verzegelde typen (string
in dit geval) niet als beperkingen kunnen worden gebruikt. Het weglaten van de beperking zou ook een fout zijn omdat beperkingen van impliciete interfacemethode-implementaties vereist zijn om overeen te komen. Het is dus onmogelijk impliciet te implementerenI<object,C,string>.H<T>
. Deze interfacemethode kan alleen worden geïmplementeerd met behulp van een expliciete implementatie van interfaceleden: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 dit geval roept de expliciete implementatie van interfaceleden een openbare methode aan met strikt zwakkere beperkingen. De toewijzing van t-to-s is geldig omdat
T
een beperking wordt overgenomen,T: string
ook al is deze beperking niet uit te drukken in de broncode. eindvoorbeeld
Opmerking: Wanneer een algemene methode expliciet een interfacemethode implementeert, zijn er geen beperkingen toegestaan voor de implementatiemethode (§15.7.1, §18.6.2). eindnotitie
18.6.5 Interfacetoewijzing
Een klasse of struct biedt implementaties van alle leden van de interfaces die worden vermeld in de basisklasselijst van de klasse of struct. Het proces voor het vinden van implementaties van interfaceleden in een implementatieklasse of struct wordt interfacetoewijzing genoemd.
Interfacetoewijzing voor een klasse of struct C
zoekt een implementatie voor elk lid van elke interface die is opgegeven in de basisklasselijst van C
. De implementatie van een bepaald interfacelid I.M
, waarbij I
de interface waarin het lid M
wordt gedeclareerd, wordt bepaald door elke klasse of struct S
te onderzoeken, te beginnen met C
en te herhalen voor elke opeenvolgende basisklasse van C
, totdat een overeenkomst zich bevindt:
- Als
S
dit een declaratie bevat van een expliciete interfacelid-implementatie die overeenkomtI
enM
, dan is dit lid de implementatie vanI.M
. - Als dit geen statisch openbaar lid bevat dat overeenkomt
S
,M
is dit lid de implementatie vanI.M
. Als er meer dan één lid overeenkomt, wordt niet opgegeven van welk lid de implementatieI.M
is. Deze situatie kan alleen optreden alsS
een samengesteld type is waarbij de twee leden die in het algemene type zijn gedeclareerd, verschillende handtekeningen hebben, maar de typeargumenten hun handtekeningen identiek maken.
Er treedt een compilatiefout op als implementaties niet kunnen worden gevonden voor alle leden van alle interfaces die zijn opgegeven in de basisklasselijst van C
. De leden van een interface omvatten de leden die worden overgenomen van basisinterfaces.
Leden van een samengesteld interfacetype worden beschouwd als typen parameters vervangen door de bijbehorende typeargumenten zoals opgegeven in §15.3.3.
Voorbeeld: Bijvoorbeeld, op basis van de algemene interfacedeclaratie:
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }
de samengestelde interface
I<string[]>
heeft de leden:string[] F(int x, string[,][] y); string[] this[int y] { get; }
eindvoorbeeld
Voor interfacetoewijzing komt een klasse- of structlid overeen met een interfacelid A
B
wanneer:
-
A
enB
zijn methoden, en de naam, het type en de parameterlijsten vanA
enB
zijn identiek. -
A
enB
eigenschappen zijn, de naam en het typeA
enB
identiek zijn enA
hebben dezelfde toegangsrechten alsB
(A
is toegestaan om extra toegangsrechten te hebben als het geen expliciete implementatie van interfaceleden is). -
A
enB
gebeurtenissen zijn, en de naam en het typeA
enB
zijn identiek. -
A
enB
zijn indexeerfuncties, het type en de parameterlijsten vanA
enB
zijn identiek enA
hebben dezelfde toegangsrechten alsB
(A
is toegestaan om extra toegangsors te hebben als het geen expliciete implementatie van interfaceleden is).
Belangrijke gevolgen van het algoritme voor interfacetoewijzing zijn:
- Expliciete implementaties van interfaceleden hebben voorrang op andere leden in dezelfde klasse of struct bij het bepalen van de klasse of het struct-lid dat een interfacelid implementeert.
- Niet-openbare of statische leden nemen geen deel aan interfacetoewijzing.
Voorbeeld: In de volgende code
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
Het
ICloneable.Clone
-lid vanC
wordt de implementatie vanClone
inICloneable
omdat expliciete implementaties van interfaceleden prioriteit hebben boven andere leden.eindvoorbeeld
Als een klasse of struct twee of meer interfaces implementeert die een lid met dezelfde naam, hetzelfde type en dezelfde parametertypen bevatten, is het mogelijk om elk van deze interfaceleden toe te wijzen aan één klasse of struct-lid.
Voorbeeld:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
Hier worden de
Paint
methoden van beideIControl
enIForm
toegewezen aan dePaint
methode inPage
. Het is natuurlijk ook mogelijk om afzonderlijke expliciete interfacelid-implementaties te hebben voor de twee methoden.eindvoorbeeld
Als een klasse of struct een interface implementeert die verborgen leden bevat, moeten sommige leden mogelijk worden geïmplementeerd via expliciete implementaties van interfaceleden.
Voorbeeld:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
Een implementatie van deze interface vereist ten minste één expliciete implementatie van het interfacelid en zou een van de volgende vormen aannemen
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() {...} }
eindvoorbeeld
Wanneer een klasse meerdere interfaces implementeert die dezelfde basisinterface hebben, kan er slechts één implementatie van de basisinterface zijn.
Voorbeeld: In de volgende 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) {...} }
het is niet mogelijk om afzonderlijke implementaties te hebben voor de
IControl
benoemde in de basisklasselijst, deIControl
overgenomen doorITextBox
en deIControl
overgenomen doorIListBox
. Er bestaat namelijk geen idee van een afzonderlijke identiteit voor deze interfaces. De implementaties van enITextBox
delen dezelfde implementatie van , enIListBox
worden gewoon beschouwd als het implementeren vanIControl
ComboBox
drie interfaces,IControl
,ITextBox
enIListBox
.eindvoorbeeld
De leden van een basisklasse nemen deel aan interfacetoewijzing.
Voorbeeld: In de volgende code
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
de methode
F
Class1
wordt gebruikt bijClass2's
de uitvoering vanInterface1
.eindvoorbeeld
18.6.6 Interface-implementatie overname
Een klasse neemt alle interface-implementaties over die worden geleverd door de basisklassen.
Zonder expliciet een interface opnieuw te implementeren, kan een afgeleide klasse de interfacetoewijzingen die worden overgenomen van de basisklassen niet wijzigen.
Voorbeeld: In de declaraties
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
de
Paint
methode inTextBox
verbergt dePaint
methode inControl
, maar de toewijzing vanControl.Paint
IControl.Paint
de methode wordt niet gewijzigd en aanroepen viaPaint
klasse-exemplaren en interface-exemplaren hebben de volgende effectenControl 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();
eindvoorbeeld
Wanneer een interfacemethode echter is toegewezen aan een virtuele methode in een klasse, is het mogelijk dat afgeleide klassen de virtuele methode overschrijven en de implementatie van de interface wijzigen.
Voorbeeld: De bovenstaande declaraties herschrijven naar
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }
de volgende effecten worden nu waargenomen
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();
eindvoorbeeld
Omdat expliciete implementaties van interfaceleden niet virtueel kunnen worden gedeclareerd, is het niet mogelijk om een expliciete implementatie van interfaceleden te overschrijven. Het is echter perfect geldig voor een expliciete implementatie van interfaceleden om een andere methode aan te roepen en die andere methode kan worden gedeclareerd als virtueel, zodat afgeleide klassen deze kunnen overschrijven.
Voorbeeld:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }
Hier kunnen klassen die zijn afgeleid van
Control
, de implementatie vanIControl.Paint
de methode specialiseren door dePaintControl
methode te overschrijven.eindvoorbeeld
18.6.7 Interface her-implementatie
Een klasse die een interface-implementatie over neemt, kan de interface opnieuw implementeren door deze op te nemen in de lijst met basisklassen.
Een her-implementatie van een interface volgt precies dezelfde interfacetoewijzingsregels als een eerste implementatie van een interface. De overgenomen interfacetoewijzing heeft dus geen enkel effect op de interfacetoewijzing die is vastgesteld voor de her-implementatie van de interface.
Voorbeeld: In de declaraties
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }
het feit waarop
Control
wordt toegewezenIControl.Paint
Control.IControl.Paint
, heeft geen invloed op de heruitvoering inMyControl
, die wordt toegewezenIControl.Paint
aanMyControl.Paint
.eindvoorbeeld
Overgenomen declaraties van openbare leden en overgenomen expliciete interfaceliddeclaraties nemen deel aan het interfacetoewijzingsproces voor opnieuw geïmplementeerde interfaces.
Voorbeeld:
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 wijst de implementatie van
IMethods
inDerived
kaart de interfacemethoden toe aanDerived.F
,Base.IMethods.G
,Derived.IMethods.H
enBase.I
.eindvoorbeeld
Wanneer een klasse een interface implementeert, implementeert deze impliciet ook alle basisinterfaces van die interface. Een her-implementatie van een interface is ook impliciet een her-implementatie van alle basisinterfaces van de interface.
Voorbeeld:
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 wordt ook de her-implementatie van
IDerived
ook opnieuw geïmplementeerdIBase
, toewijzingIBase.F
aanD.F
.eindvoorbeeld
18.6.8 Abstracte klassen en interfaces
Net als een niet-abstracte klasse biedt een abstracte klasse implementaties van alle leden van de interfaces die worden vermeld in de lijst met basisklassen van de klasse. Een abstracte klasse is echter toegestaan om interfacemethoden toe te wijzen aan abstracte methoden.
Voorbeeld:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
Hier wordt de implementatie van
IMethods
kaartenF
enG
op abstracte methoden, die worden overschreven in niet-abstracte klassen die zijn afgeleid vanC
.eindvoorbeeld
Expliciete implementaties van interfaceleden kunnen niet abstract zijn, maar expliciete implementaties van interfaceleden zijn natuurlijk toegestaan abstracte methoden aan te roepen.
Voorbeeld:
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 zouden niet-abstracte klassen die zijn afgeleid
C
, moeten worden overschrevenFF
enGG
, waardoor de daadwerkelijke implementatie vanIMethods
.eindvoorbeeld
ECMA C# draft specification