18 gränssnitt
18.1 Allmänt
Ett gränssnitt definierar ett kontrakt. En klass eller struct som implementerar ett gränssnitt ska följa avtalet. Ett gränssnitt kan ärva från flera basgränssnitt, och en klass eller struct kan implementera flera gränssnitt.
Gränssnitt kan innehålla metoder, egenskaper, händelser och indexerare. Själva gränssnittet tillhandahåller inte implementeringar för de medlemmar som det deklarerar. Gränssnittet anger bara de medlemmar som ska tillhandahållas av klasser eller structs som implementerar gränssnittet.
18.2 Gränssnittsdeklarationer
18.2.1 Allmänt
En interface_declaration är en type_declaration (§14.7) som deklarerar en ny gränssnittstyp.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
En interface_declaration består av en valfri uppsättning attribut (§22), följt av en valfri uppsättning interface_modifier(§18.2.2), följt av en valfri partiell modifierare (§15.2.7), följt av nyckelordet interface
och en identifierare som namnger gränssnittet, följt av en valfri variant_type_parameter_list specifikation (§18.2.3), följt av en valfri interface_base specifikation (§18.2.4), följt av en valfri type_parameter_constraints_clauses specifikation (§15.2.5), följt av en interface_body (§18.3), eventuellt följt av semikolon.
En gränssnittsdeklaration får inte tillhandahålla en type_parameter_constraints_clauseom den inte även tillhandahåller en variant_type_parameter_list.
En gränssnittsdeklaration som tillhandahåller en variant_type_parameter_list är en allmän gränssnittsdeklaration. Dessutom är alla gränssnitt kapslade i en generisk klassdeklaration eller en generisk structdeklaration i sig en allmän gränssnittsdeklaration, eftersom typargument för den innehållande typen ska tillhandahållas för att skapa en konstruerad typ (§8.4).
18.2.2 Gränssnittsmodifierare
En interface_declaration kan eventuellt innehålla en sekvens med gränssnittsmodifierare:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
Det är ett kompileringsfel för samma modifierare som ska visas flera gånger i en gränssnittsdeklaration.
Modifieraren new
tillåts endast för gränssnitt som definierats i en klass. Det anger att gränssnittet döljer en ärvd medlem med samma namn, enligt beskrivningen i §15.3.5.
Modifierarna public
, protected
, internal
och private
styr gränssnittets tillgänglighet. Beroende på i vilken kontext gränssnittsdeklarationen inträffar kan endast vissa av dessa modifierare tillåtas (§7.5.2). När en partiell typdeklaration (§15.2.7) innehåller en tillgänglighetsspecifikation (via public
, protected
, internal
och private
modifierare), gäller reglerna i §15.2.2 .
18.2.3 Parameterlistor av varianttyp
18.2.3.1 Allmänt
Parameterlistor av varianttyp kan bara förekomma i gränssnitts- och ombudstyper. Skillnaden från vanliga type_parameter_listär den valfria variance_annotation för varje typparameter.
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'
;
Om variansanteckningen är out
, sägs typparametern vara covariant. Om variansanteckningen är in
, sägs typparametern vara kontravariant. Om det inte finns någon variansanteckning sägs typparametern vara invariant.
Exempel: I följande:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
är covariant,Y
är kontravariant ochZ
är invariant.slutexempel
Om ett allmänt gränssnitt deklareras i flera delar (§15.2.3) ska varje partiell deklaration ange samma varians för varje typparameter.
18.2.3.2 Avvikelsesäkerhet
Förekomsten av variansanteckningar i typparameterlistan för en typ begränsar de platser där typer kan förekomma i typdeklarationen.
En typ T är utdatasäker om något av följande gäller:
-
T
är en parameter av typen contravariant -
T
är en matristyp med en typ av utdatasäkert element -
T
är en gränssnitts- eller delegattypSᵢ,... Aₑ
som konstruerats av en allmän typS<Xᵢ, ... Xₑ>
där minst enAᵢ
av följande finns:-
Xᵢ
är variant eller invariant ochAᵢ
är inte säker på utdata. -
Xᵢ
är kontravariant eller invariant ochAᵢ
är indatasäker.
-
En typ T är indatasäker om något av följande gäller:
-
T
är en parameter av typen covariant -
T
är en matristyp med en indatasäker elementtyp -
T
är en gränssnitts- eller delegattypS<Aᵢ,... Aₑ>
som konstruerats av en allmän typS<Xᵢ, ... Xₑ>
där minst enAᵢ
av följande finns:-
Xᵢ
är kovariant eller invariant ochAᵢ
är indatasäkert. -
Xᵢ
är kontravariant eller invariant ochAᵢ
är inte säker på utdata.
-
Intuitivt är en typ av osäker utdata förbjuden i en utdataposition och en typ av indata som är osäker är förbjuden i indatapositionen.
En typ är utdatasäker om den inte är utdatasäker och indatasäker om den inte är indatasäker.
18.2.3.3 Varianskonvertering
Syftet med variansanteckningar är att tillhandahålla mer överseende (men ändå skriva säkra) konverteringar till gränssnitts- och ombudstyper. För detta ändamål använder definitionerna av implicita (§10.2) och explicita konverteringar (§10.3) begreppet varianskonverterbarhet, som definieras på följande sätt:
En typ T<Aᵢ, ..., Aᵥ>
är varianskonverterad till en typ T<Bᵢ, ..., Bᵥ>
om T
är antingen ett gränssnitt eller en ombudstyp som deklareras med parametrarna T<Xᵢ, ..., Xᵥ>
för varianttypen och för varje varianttypsparameter Xᵢ
något av följande undantag:
-
Xᵢ
är covariant och det finns en implicit referens- eller identitetskonvertering frånAᵢ
tillBᵢ
-
Xᵢ
är kontravariant och det finns en implicit referens eller identitetskonvertering frånBᵢ
tillAᵢ
-
Xᵢ
är invariant och det finns en identitetskonvertering frånAᵢ
tillBᵢ
18.2.4 Basgränssnitt
Ett gränssnitt kan ärva från noll eller flera gränssnittstyper, som kallas explicita basgränssnitt för gränssnittet. När ett gränssnitt har ett eller flera explicita basgränssnitt följs gränssnittsidentifieraren i deklarationen av gränssnittet av ett kolon och en kommaavgränsad lista över basgränssnittstyper.
interface_base
: ':' interface_type_list
;
De explicita basgränssnitten kan vara konstruerade gränssnittstyper (§8.4, §18.2). Ett basgränssnitt kan inte vara en typparameter på egen hand, även om det kan omfatta de typparametrar som finns i omfånget.
För en konstruerad gränssnittstyp bildas de explicita basgränssnitten genom att de explicita basgränssnittsdeklarationerna tas på den generiska typdeklarationen, och för varje type_parameter i basgränssnittsdeklarationen ersätts motsvarande type_argument av den konstruerade typen.
Gränssnittens explicita basgränssnitt ska vara minst lika tillgängliga som själva gränssnittet (§7.5.5).
Obs! Det är till exempel ett kompileringsfel att ange ett
private
ellerinternal
ett gränssnitt i interface_base för ettpublic
gränssnitt. slutkommentar
Det är ett kompileringsfel för ett gränssnitt som direkt eller indirekt ärver från sig självt.
Basgränssnitten i ett gränssnitt är de explicita basgränssnitten och deras basgränssnitt. Med andra ord är uppsättningen med basgränssnitt den fullständiga transitiva stängningen av de explicita basgränssnitten, deras explicita basgränssnitt och så vidare. Ett gränssnitt ärver alla medlemmar i dess basgränssnitt.
Exempel: I följande kod
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
basgränssnitten
IComboBox
för ärIControl
,ITextBox
ochIListBox
. Med andra ordIComboBox
ärver gränssnittet ovan medlemmarSetText
ochSetItems
samtPaint
.slutexempel
Medlemmar som ärvs från en konstruerad generisk typ ärvs efter typersättning. Det innebär att alla komponenttyper i medlemmen har basklassdeklarationens typparametrar ersatta med motsvarande typargument som används i class_base-specifikationen.
Exempel: I följande kod
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
gränssnittet
IDerived
ärverCombine
metoden efter att typparameternT
har ersatts medstring[,]
.slutexempel
En klass eller struct som implementerar ett gränssnitt implementerar också implicit alla gränssnittets basgränssnitt.
Hanteringen av gränssnitt på flera delar av en partiell gränssnittsdeklaration (§15.2.7) diskuteras ytterligare i §15.2.4.3.
Varje basgränssnitt i ett gränssnitt ska vara utdatasäkert (§18.2.3.2).
18.3 Gränssnittstext
Interface_body i ett gränssnitt definierar medlemmarna i gränssnittet.
interface_body
: '{' interface_member_declaration* '}'
;
18.4 Gränssnittsmedlemmar
18.4.1 Allmänt
Medlemmarna i ett gränssnitt är de medlemmar som ärvs från basgränssnitten och de medlemmar som deklareras av själva gränssnittet.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
En gränssnittsdeklaration deklarerar noll eller fler medlemmar. Medlemmarna i ett gränssnitt ska vara metoder, egenskaper, händelser eller indexerare. Ett gränssnitt får inte innehålla konstanter, fält, operatorer, instanskonstruktorer, finalizers eller typer, och inte heller kan ett gränssnitt innehålla statiska medlemmar av något slag.
Alla gränssnittsmedlemmar har implicit offentlig åtkomst. Det är ett kompileringsfel för medlemsdeklarationer för gränssnittet för att inkludera eventuella modifierare.
En interface_declaration skapar ett nytt deklarationsutrymme (§7.3), och typparametrarna och interface_member_declarationsom omedelbart ingår i interface_declaration introducera nya medlemmar i detta deklarationsutrymme. Följande regler gäller för interface_member_declarations:
- Namnet på en typparameter i variant_type_parameter_list i en gränssnittsdeklaration ska skilja sig från namnen på alla andra typparametrar i samma variant_type_parameter_list och ska skilja sig från namnen på alla medlemmar i gränssnittet.
- Namnet på en metod ska skilja sig från namnen på alla egenskaper och händelser som deklareras i samma gränssnitt. Dessutom ska signaturen (§7.6) för en metod skilja sig från signaturerna för alla andra metoder som deklarerats i samma gränssnitt, och två metoder som deklarerats i samma gränssnitt får inte ha signaturer som skiljer sig enbart
in
från ,out
ochref
. - Namnet på en egendom eller händelse ska skilja sig från namnen på alla andra medlemmar som deklareras i samma gränssnitt.
- En indexerares signatur ska skilja sig från signaturerna för alla andra indexerare som deklarerats i samma gränssnitt.
De ärvda medlemmarna i ett gränssnitt är specifikt inte en del av gränssnittets deklarationsutrymme. Därför kan ett gränssnitt deklarera en medlem med samma namn eller signatur som en ärvd medlem. När detta inträffar sägs den härledda gränssnittsmedlemmen dölja basgränssnittsmedlemmen. Att dölja en ärvd medlem betraktas inte som ett fel, men det gör att en kompilator utfärdar en varning. För att förhindra varningen ska deklarationen av den härledda gränssnittsmedlemmen innehålla en new
modifierare som anger att den härledda medlemmen är avsedd att dölja basmedlemmen. Detta ämne diskuteras vidare i §7.7.2.3.
Om en new
modifierare ingår i en deklaration som inte döljer en ärvd medlem utfärdas en varning om detta. Den här varningen ignoreras genom att modifieraren tas bort new
.
Obs! Medlemmarna i klassen
object
är inte, strängt taget, medlemmar i något gränssnitt (§18.4). Medlemmarna i klassenobject
är dock tillgängliga via medlemssökning i alla gränssnittstyper (§12.5). slutkommentar
Uppsättningen medlemmar i ett gränssnitt som deklarerats i flera delar (§15.2.7) är föreningen av medlemmarna som deklareras i varje del. Organen i alla delar av gränssnittsdeklarationen delar samma deklarationsutrymme (§7.3), och omfånget för varje medlem (§7.7) sträcker sig till alla delars organ.
18.4.2 Gränssnittsmetoder
Gränssnittsmetoder deklareras med 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* ';'
;
Attributen, return_type, ref_return_type, identifierare och parameter_list av en gränssnittsmetoddeklaration har samma betydelse som för en metoddeklaration i en klass (§15.6). En deklaration av gränssnittsmetod tillåts inte att ange en metodtext, och deklarationen slutar därför alltid med semikolon.
Alla parametertyper för en gränssnittsmetod ska vara indatasäkra (§18.2.3.2) och returtypen ska vara antingen void
eller utgångssäker. Dessutom ska alla typer av utdata eller referensparametrar också vara utdatasäkra.
Obs! Utdataparametrar måste vara indatasäkra på grund av vanliga implementeringsbegränsningar. slutkommentar
Dessutom ska varje klasstypsbegränsning, gränssnittstypsbegränsning och typparameterbegränsning för alla typparametrar i metoden vara indatasäkra.
Dessutom ska varje klasstypsbegränsning, gränssnittstypsbegränsning och typparameterbegränsning för alla typparametrar i metoden vara indatasäkra.
Dessa regler säkerställer att all samtidig eller kontravariant användning av gränssnittet förblir typesafe.
Exempel:
interface I<out T> { void M<U>() where U : T; // Error }
är dåligt utformad eftersom användningen av
T
som en typparameterbegränsningU
inte är indatasäker.Om den här begränsningen inte hade införts skulle det vara möjligt att bryta mot typsäkerheten på följande sätt:
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
Det här är faktiskt ett anrop till
C.M<E>
. Men det samtalet kräver att härledasE
frånD
, så typ säkerhet skulle brytas här.slutexempel
18.4.3 Gränssnittsegenskaper
Gränssnittsegenskaper deklareras med 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' ';'
;
Attributen, typen och identifieraren för en gränssnittsegenskapsdeklaration har samma betydelse som egenskaperna för en egenskapsdeklaration i en klass (§15.7).
Åtkomsten till en gränssnittsegenskapsdeklaration motsvarar de som har tillgång till en klassegenskapsdeklaration (§15.7.3), förutom att accessor_body alltid ska vara semikolon. Därför anger åtkomsterna helt enkelt om egenskapen är skrivskyddad, skrivskyddad eller skrivskyddad.
Typen av en gränssnittsegenskap ska vara utdatasäker om det finns en get-accessor och ska vara indatasäker om det finns en fast accessor.
18.4.4 Gränssnittshändelser
Gränssnittshändelser deklareras med hjälp av interface_event_declarations:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
Attributen, typen och identifieraren för en gränssnittshändelsedeklaration har samma betydelse som en händelsedeklaration i en klass (§15.8).
Typen av gränssnittshändelse ska vara indatasäker.
18.4.5 Gränssnittsindexerare
Gränssnittsindexerare deklareras med 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 '}'
;
Attributen, typen och parameter_list för en deklaration av gränssnittsindexerare har samma betydelse som en indexerardeklaration i en klass (§15.9).
Åtkomsterna till en deklaration av gränssnittsindexerare motsvarar de som har tillgång till en klassindexeringsdeklaration (§15.9), förutom att accessor_body alltid ska vara semikolon. Därför anger åtkomsterna helt enkelt om indexeraren är skrivskyddad, skrivskyddad eller skrivskyddad.
Alla parametertyper för en gränssnittsindexerare ska vara indatasäkra (§18.2.3.2). Dessutom ska alla typer av utdata eller referensparametrar också vara utdatasäkra.
Obs! Utdataparametrar måste vara indatasäkra på grund av vanliga implementeringsbegränsningar. slutkommentar
Typen av gränssnittsindexerare ska vara utdatasäker om det finns en get-accessor och vara indatasäker om det finns en fast accessor.
18.4.6 Gränssnittsmedlemsåtkomst
Gränssnittsmedlemmar nås via medlemsåtkomst (§12.8.7) och indexeraråtkomst (§12.8.12.3) uttryck för formuläret I.M
och I[A]
, där I
är en gränssnittstyp, M
är en metod, egenskap eller händelse av den gränssnittstypen och A
är en indexerares argumentlista.
För gränssnitt som är strikt enkelarv (där varje gränssnitt i arvskedjan har exakt noll eller ett direkt basgränssnitt), är effekterna av regler för medlemssökning (§12.5), metodanrop (§12.8.10.2), och indexeråtkomst (§12.8.12.3) exakt samma som för klasser och strukturer: Fler härledda medlemmar döljer mindre härledda medlemmar med samma namn eller signatur. För gränssnitt med flera arv kan dock tvetydigheter uppstå när två eller flera orelaterade basgränssnitt deklarerar medlemmar med samma namn eller signatur. Den här underklienten visar flera exempel, varav vissa leder till tvetydigheter och andra som inte gör det. I samtliga fall kan explicita avgjutningar användas för att lösa tvetydigheterna.
Exempel: I följande kod
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 första två uttrycken orsakar kompileringsfel eftersom medlemssökningen (§12.5)
Count
iIListCounter
är tvetydig. Som illustreras av exemplet löses tvetydigheten genom gjutningx
till lämplig basgränssnittstyp. Sådana avgjutningar har inga körningskostnader – de består bara av att visa instansen som en mindre härledd typ vid kompileringstid.slutexempel
Exempel: I följande kod
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 } }
anropet väljer genom att tillämpa regler för överbelastningsmatchning
n.Add(1)
IInteger.Add
i §12.6.4. På samma sätt väljern.Add(1.0)
anropetIDouble.Add
. När explicita avgjutningar infogas finns det bara en kandidatmetod och därmed ingen tvetydighet.slutexempel
Exempel: I följande kod
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 } }
medlemmen
IBase.F
är dold avILeft.F
medlemmen. Anropetd.F(1)
väljerILeft.F
därför , även omIBase.F
det inte verkar vara dolt i åtkomstsökvägen som leder genomIRight
.Den intuitiva regeln för att gömma sig i gränssnitt med flera arv är helt enkelt följande: Om en medlem är dold i någon åtkomstsökväg är den dold i alla åtkomstvägar. Eftersom åtkomstsökvägen från
IDerived
tillILeft
tillIBase
döljerIBase.F
är medlemmen också dold i åtkomstsökvägen frånIDerived
tillIRight
.IBase
slutexempel
18.5 Namn på kvalificerade gränssnittsmedlemmar
En gränssnittsmedlem kallas ibland för dess kvalificerade gränssnittsmedlemsnamn. Det kvalificerade namnet på en gränssnittsmedlem består av namnet på gränssnittet där medlemmen deklareras, följt av en punkt följt av namnet på medlemmen. Det kvalificerade namnet på en medlem refererar till gränssnittet där medlemmen deklareras.
Exempel: Med tanke på deklarationerna
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
det kvalificerade namnet på
Paint
ärIControl.Paint
och det kvalificerade namnet på SetText ärITextBox.SetText
. I exemplet ovan går det inte att referera tillPaint
somITextBox.Paint
.slutexempel
När ett gränssnitt ingår i ett namnområde kan ett kvalificerat gränssnittsmedlemsnamn innehålla namnområdesnamnet.
Exempel:
namespace System { public interface ICloneable { object Clone(); } }
System
I namnområdet är bådeICloneable.Clone
ochSystem.ICloneable.Clone
kvalificerade gränssnittsmedlemsnamn förClone
metoden.slutexempel
18.6 Gränssnittsimplementeringar
18.6.1 Allmänt
Gränssnitt kan implementeras av klasser och structs. För att indikera att en klass eller struct implementerar ett gränssnitt direkt, inkluderas gränssnittet i basklasslistan för klassen eller structen.
Exempel:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
slutexempel
En klass eller struct som direkt implementerar ett gränssnitt implementerar också implicit alla gränssnittets basgränssnitt. Detta gäller även om klassen eller structen inte uttryckligen visar alla basgränssnitt i basklasslistan.
Exempel:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
Här implementerar klassen
TextBox
bådeIControl
ochITextBox
.slutexempel
När en klass C
implementerar ett gränssnitt direkt implementerar alla klasser som härleds från C
gränssnittet implicit.
De basgränssnitt som anges i en klassdeklaration kan vara konstruerade gränssnittstyper (§8.4, §18.2).
Exempel: Följande kod illustrerar hur en klass kan implementera konstruerade gränssnittstyper:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
slutexempel
Basgränssnitten i en generisk klassdeklaration ska uppfylla den unika regel som beskrivs i §18.6.3.
18.6.2 Explicita implementeringar av gränssnittsmedlemmar
I syfte att implementera gränssnitt kan en klass eller struct deklarera explicita implementeringar av gränssnittsmedlemmar. En explicit implementering av gränssnittsmedlemmar är en metod, egenskap, händelse eller indexerare som refererar till ett kvalificerat gränssnittsmedlemsnamn.
Exempel:
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) {...} }
Här
IDictionary<int,T>.this
ochIDictionary<int,T>.Add
är explicita implementeringar av gränssnittsmedlemmar.slutexempel
Exempel: I vissa fall kanske namnet på en gränssnittsmedlem inte är lämpligt för implementeringsklassen. I så fall kan gränssnittsmedlemmen implementeras med explicit implementering av gränssnittsmedlemmar. En klass som implementerar en filabstraktion skulle till exempel sannolikt implementera en
Close
medlemsfunktion som har effekten att släppa filresursen och implementeraDispose
metoden för gränssnittet med explicit implementering avIDisposable
gränssnittsmedlemmar: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); } }
slutexempel
Det går inte att komma åt en explicit implementering av gränssnittsmedlemmar via dess kvalificerade gränssnittsmedlemsnamn i en metodanrop, egenskapsåtkomst, händelseåtkomst eller indexerareåtkomst. En explicit implementering av gränssnittsmedlemmar kan bara nås via en gränssnittsinstans och refereras i så fall bara av dess medlemsnamn.
Det är ett kompileringsfel för en explicit implementering av gränssnittsmedlemmar för att inkludera andra modifierare (§15.6) än extern
eller async
.
Det är ett kompileringsfel för en explicit implementering av gränssnittsmetod för att inkludera type_parameter_constraints_clauses. Begränsningarna för en allmän explicit gränssnittsmetodimplementering ärvs från gränssnittsmetoden.
Obs! Explicita implementeringar av gränssnittsmedlemmar har andra hjälpmedelsegenskaper än andra medlemmar. Eftersom explicita implementeringar av gränssnittsmedlemmar aldrig är tillgängliga via ett kvalificerat gränssnittsmedlemsnamn i ett metodanrop eller en egenskapsåtkomst, är de på sätt och vis privata. Men eftersom de kan nås via gränssnittet är de på sätt och vis lika offentliga som gränssnittet där de deklareras. Explicita implementeringar av gränssnittsmedlemmar har två huvudsakliga syften:
- Eftersom explicita implementeringar av gränssnittsmedlemmar inte är tillgängliga via klass- eller struct-instanser tillåter de att gränssnittsimplementeringar undantas från det offentliga gränssnittet för en klass eller struct. Detta är särskilt användbart när en klass eller struct implementerar ett internt gränssnitt som inte är av intresse för en konsument av den klassen eller struct.
- Explicita implementeringar av gränssnittsmedlemmar tillåter tvetydighet för gränssnittsmedlemmar med samma signatur. Utan explicita implementeringar av gränssnittsmedlemmar skulle det vara omöjligt för en klass eller struct att ha olika implementeringar av gränssnittsmedlemmar med samma signatur och returtyp, vilket skulle vara omöjligt för en klass eller struct att ha någon implementering alls av gränssnittsmedlemmar med samma signatur men med olika returtyper.
slutkommentar
För att en explicit implementering av gränssnittsmedlemmar ska vara giltig ska klassen eller structen namnge ett gränssnitt i sin basklasslista som innehåller en medlem vars kvalificerade gränssnittsmedlemsnamn, typ, antal typparametrar och parametertyper exakt matchar dem för den explicita implementeringen av gränssnittsmedlemmar. Om en gränssnittsfunktionsmedlem har en parametermatris tillåts motsvarande parameter för en associerad explicit implementering av gränssnittsmedlemmar, men krävs inte, att ha params
modifieraren. Om gränssnittsfunktionsmedlemmen inte har någon parametermatris ska en associerad explicit gränssnittsmedlemimplementering inte ha någon parametermatris.
Exempel: I följande klass
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
deklarationen av
IComparable.CompareTo
resulterar i ett kompileringsfel eftersomIComparable
den inte finns med i basklasslistan förShape
och inte är ett basgränssnitt förICloneable
. På samma sätt, i deklarationernaclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
deklarationen av
ICloneable.Clone
i resulterar iEllipse
ett kompileringsfel eftersomICloneable
det inte uttryckligen anges i basklasslistan förEllipse
.slutexempel
Det kvalificerade gränssnittsmedlemsnamnet för en explicit gränssnittsmedlemsimplementering ska referera till det gränssnitt där medlemmen deklarerades.
Exempel: I deklarationerna
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
den explicita gränssnittsmedlemsimplementeringen av Paint måste skrivas som
IControl.Paint
, inteITextBox.Paint
.slutexempel
18.6.3 Unikhet för implementerade gränssnitt
De gränssnitt som implementeras av en generisk typdeklaration ska förbli unika för alla möjliga konstruktionstyper. Utan den här regeln skulle det vara omöjligt att fastställa rätt metod för att anropa vissa konstruerade typer.
Exempel: Anta att en allmän klassdeklaration tilläts skrivas på följande sätt:
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() {...} }
Om detta tillåts skulle det vara omöjligt att avgöra vilken kod som ska köras i följande fall:
I<int> x = new X<int, int>(); x.F();
slutexempel
Följande steg utförs för att avgöra om gränssnittslistan för en allmän typdeklaration är giltig:
- Låt oss
L
vara en lista över gränssnitt som har angetts direkt i en allmän klass-, struct- eller gränssnittsdeklarationC
. - Lägg till
L
i alla basgränssnitt för gränssnitten som redan finns iL
. - Ta bort eventuella dubbletter från
L
. - Om någon möjlig konstruerad typ som skapats från
C
skulle, efter att typargument har ersatts medL
, orsaka två gränssnitt iL
vara identiska, är deklarationen avC
ogiltig. Villkorsdeklarationer beaktas inte när du fastställer alla möjliga konstruerade typer.
Obs! I klassdeklarationen
X
ovan består gränssnittslistanL
avl<U>
ochI<V>
. Deklarationen är ogiltig eftersom en konstruerad typ medU
ochV
samma typ skulle göra att dessa två gränssnitt är identiska typer. slutkommentar
Det är möjligt att gränssnitt som anges på olika arvsnivåer förenas:
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() {...}
}
Den här koden är giltig även om Derived<U,V>
implementerar både I<U>
och I<V>
. Koden
I<int> x = new Derived<int, int>();
x.F();
anropar metoden i , eftersom Derived
den effektivt implementerar Derived<int,int>'
om (I<int>
).
18.6.4 Implementering av generiska metoder
När en allmän metod implicit implementerar en gränssnittsmetod ska de begränsningar som anges för varje metodtypparameter vara likvärdiga i båda deklarationerna (efter att eventuella gränssnittstypparametrar har ersatts med lämpliga typargument), där metodtypparametrar identifieras med ordningstal, från vänster till höger.
Exempel: I följande kod:
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 }
metoden
C.F<T>
implementerarI<object,C,string>.F<T>
implicit . I det här falletC.F<T>
krävs inte (eller tillåts) för att ange villkoretT: object
eftersomobject
är en implicit begränsning för alla typparametrar. MetodenC.G<T>
implementerarI<object,C,string>.G<T>
implicit eftersom begränsningarna matchar dem i gränssnittet, efter att parametrarna för gränssnittstypen har ersatts med motsvarande typargument. Villkoret för metodenC.H<T>
är ett fel eftersom förseglade typer (string
i det här fallet) inte kan användas som begränsningar. Att utelämna villkoret skulle också vara ett fel eftersom begränsningar för implementeringar av implicita gränssnittsmetoder krävs för att matcha. Därför är det omöjligt att implicit implementeraI<object,C,string>.H<T>
. Den här gränssnittsmetoden kan bara implementeras med en explicit implementering av gränssnittsmedlemmar: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); } }
I det här fallet anropar den explicita implementeringen av gränssnittsmedlemmar en offentlig metod med strikt svagare begränsningar. Tilldelningen från t till s är giltig eftersom
T
ärver en begränsning påT: string
, även om den här begränsningen inte kan uttryckas i källkoden. slutexempel
Obs! När en generisk metod uttryckligen implementerar en gränssnittsmetod tillåts inga begränsningar för implementeringsmetoden (§15.7.1, §18.6.2). slutkommentar
18.6.5 Gränssnittsmappning
En klass eller struct ska tillhandahålla implementeringar av alla medlemmar i gränssnitten som anges i basklasslistan för klassen eller structen. Processen att hitta implementeringar av gränssnittsmedlemmar i en implementeringsklass eller struct kallas för gränssnittsmappning.
Gränssnittsmappning för en klass eller struct C
letar upp en implementering för varje medlem i varje gränssnitt som anges i basklasslistan för C
. Implementeringen av en viss gränssnittsmedlem I.M
, där I
är gränssnittet där medlemmen M
deklareras, bestäms genom att undersöka varje klass eller struct S
, som börjar med C
och upprepas för varje efterföljande basklass av C
, tills en matchning finns:
- Om
S
innehåller en förklaring av en explicit gränssnittsmedlemsimplementering som matcharI
ochM
, är den här medlemmen implementeringen avI.M
. - Annars, om
S
innehåller en deklaration av en icke-statisk offentlig medlem som matcharM
, är den här medlemmen implementeringen avI.M
. Om fler än en medlem matchar är det ospecificerat vilken medlem som är implementeringen avI.M
. Den här situationen kan bara inträffa omS
är en konstruerad typ där de två medlemmarna som deklareras i den generiska typen har olika signaturer, men typargumenten gör deras signaturer identiska.
Ett kompileringsfel uppstår om implementeringar inte kan hittas för alla medlemmar i alla gränssnitt som anges i basklasslistan för C
. Medlemmarna i ett gränssnitt inkluderar de medlemmar som ärvs från basgränssnitt.
Medlemmar av en konstruerad gränssnittstyp anses ha alla typparametrar ersatta med motsvarande typargument som anges i §15.3.3.
Exempel: Med tanke på den allmänna gränssnittsdeklarationen:
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }
det konstruerade gränssnittet
I<string[]>
har medlemmarna:string[] F(int x, string[,][] y); string[] this[int y] { get; }
slutexempel
För gränssnittsmappning matchar en klass- eller structmedlem A
en gränssnittsmedlem B
när:
-
A
ochB
är metoder, och namn, typ och parameterlistor förA
ochB
är identiska. -
A
ochB
är egenskaper, namnet och typen av ochA
är identiska ochB
har samma åtkomst somA
(B
tillåts ha ytterligare åtkomst om det inte är en explicit implementering avA
gränssnittsmedlemmar). -
A
ochB
är händelser, och namnet och typen avA
ochB
är identiska. -
A
ochB
är indexerare, typen och parameterlistorna för ochA
är identiska ochB
har samma åtkomst somA
(B
tillåts ha ytterligare åtkomst om det inte är en explicit implementering avA
gränssnittsmedlemmar).
Viktiga konsekvenser av algoritmen för gränssnittsmappning är:
- Explicita implementeringar av gränssnittsmedlemmar har företräde framför andra medlemmar i samma klass eller struct när du fastställer vilken klass eller struct-medlem som implementerar en gränssnittsmedlem.
- Varken icke-offentliga eller statiska medlemmar deltar i gränssnittsmappning.
Exempel: I följande kod
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
medlemmen
ICloneable.Clone
avC
blir genomförandet avClone
i "ICloneable", därför att explicit gränssnittmedlemimplementeringar har företräde framför andra medlemmar.slutexempel
Om en klass eller struct implementerar två eller flera gränssnitt som innehåller en medlem med samma namn, typ och parametertyper, är det möjligt att mappa var och en av dessa gränssnittsmedlemmar till en enda klass- eller structmedlem.
Exempel:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
Här mappas metoderna för
Paint
bådaIControl
ochIForm
tillPaint
-metoden iPage
. Det är naturligtvis också möjligt att ha separata explicita gränssnittsmedlemimplementeringar för de två metoderna.slutexempel
Om en klass eller struct implementerar ett gränssnitt som innehåller dolda medlemmar kan vissa medlemmar behöva implementeras via explicita implementeringar av gränssnittsmedlemmar.
Exempel:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
En implementering av det här gränssnittet skulle kräva minst en explicit implementering av gränssnittsmedlemmar och skulle ha något av följande formulär
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() {...} }
slutexempel
När en klass implementerar flera gränssnitt som har samma basgränssnitt kan det bara finnas en implementering av basgränssnittet.
Exempel: I följande kod
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) {...} }
det går inte att ha separata implementeringar för de
IControl
namngivna i basklasslistan, ärvdaIControl
avITextBox
och ärvdaIControl
avIListBox
. Det finns faktiskt ingen uppfattning om en separat identitet för dessa gränssnitt. Implementeringarna avITextBox
och delar i stället samma implementering avIListBox
, ochIControl
anses helt enkelt implementera tre gränssnitt,ComboBox
,IControl
ochITextBox
IListBox
.slutexempel
Medlemmarna i en basklass deltar i gränssnittsmappning.
Exempel: I följande kod
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
-metoden
F
iClass1
används iClass2's
implementeringen avInterface1
.slutexempel
18.6.6 Arv av gränssnittsimplementering
En klass ärver alla gränssnittsimplementeringar som tillhandahålls av dess basklasser.
Utan att uttryckligen implementera ett gränssnitt kan en härledd klass inte på något sätt ändra de gränssnittsmappningar som den ärver från sina basklasser.
Exempel: I deklarationerna
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
Paint
metoden iTextBox
döljerPaint
metoden iControl
, men den ändrar inte mappningen tillControl.Paint
IControl.Paint
, och anrop tillPaint
via klassinstanser och gränssnittsinstanser får följande effekterControl 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();
slutexempel
Men när en gränssnittsmetod mappas till en virtuell metod i en klass är det möjligt för härledda klasser att åsidosätta den virtuella metoden och ändra implementeringen av gränssnittet.
Exempel: Skriva om deklarationerna ovan till
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }
följande effekter kommer nu att observeras
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();
slutexempel
Eftersom explicita implementeringar av gränssnittsmedlemmar inte kan deklareras som virtuella går det inte att åsidosätta en explicit implementering av gränssnittsmedlemmar. Det är dock helt giltigt för en explicit implementering av gränssnittsmedlemmar att anropa en annan metod, och den andra metoden kan deklareras virtuell för att tillåta härledda klasser att åsidosätta den.
Exempel:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }
Här kan klasser som härleds från
Control
specialisera implementeringen avIControl.Paint
genom attPaintControl
åsidosätta metoden.slutexempel
18.6.7 Omimplementering av gränssnitt
En klass som ärver en gränssnittsimplementering tillåts implementera gränssnittet igen genom att inkludera det i basklasslistan.
En omimplementering av ett gränssnitt följer exakt samma regler för gränssnittsmappning som en inledande implementering av ett gränssnitt. Därför har den ärvda gränssnittsmappningen ingen som helst effekt på den gränssnittsmappning som upprättats för omimplementeringen av gränssnittet.
Exempel: I deklarationerna
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }
det faktum att
Control
kartor tillIControl.Paint
Control.IControl.Paint
inte påverkar omimplementeringen iMyControl
, som mapparIControl.Paint
tillMyControl.Paint
.slutexempel
Ärvda offentliga medlemsdeklarationer och ärvda explicita medlemsdeklarationer för gränssnitt deltar i gränssnittsmappningsprocessen för om implementerade gränssnitt.
Exempel:
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() {} }
Här mappar implementeringen av
IMethods
iDerived
gränssnittsmetoderna tillDerived.F
,Base.IMethods.G
,Derived.IMethods.H
ochBase.I
.slutexempel
När en klass implementerar ett gränssnitt implementerar den implicit även alla gränssnitts basgränssnitt. På samma sätt är en omimplementering av ett gränssnitt också implicit en omimplementering av alla gränssnitts basgränssnitt.
Exempel:
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() {...} }
Här omimplementering av
IDerived
även omimplementeringarIBase
, mappningIBase.F
tillD.F
.slutexempel
18.6.8 Abstrakta klasser och gränssnitt
Precis som en icke-abstrakt klass ska en abstrakt klass tillhandahålla implementeringar av alla medlemmar i de gränssnitt som anges i klassens basklasslista. En abstrakt klass tillåts dock att mappa gränssnittsmetoder till abstrakta metoder.
Exempel:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
Här ska implementeringen av
IMethods
kartorF
ochG
abstrakta metoder åsidosättas i icke-abstrakta klasser som härleds frånC
.slutexempel
Explicita implementeringar av gränssnittsmedlemmar kan inte vara abstrakta, men explicita implementeringar av gränssnittsmedlemmar tillåts naturligtvis att anropa abstrakta metoder.
Exempel:
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(); }
Här krävs icke-abstrakta klasser som härleds från
C
för att åsidosättaFF
ochGG
, vilket ger den faktiska implementeringen avIMethods
.slutexempel
ECMA C# draft specification