Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
19.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 olika typer av medlemmar, enligt beskrivningen i §19.4. Själva gränssnittet kan tillhandahålla en implementering för vissa eller alla funktionsmedlemmar som deklareras. Medlemmar som gränssnittet inte tillhandahåller någon implementering för är abstrakta. Implementeringarna måste tillhandahållas av klasser eller strukturer som implementerar gränssnittet eller det härledda gränssnittet som tillhandahåller en övergripande definition.
Obs! Tidigare påverkade tillägg av en ny funktionsmedlem i ett gränssnitt alla befintliga användare av den gränssnittstypen. Det var en icke-bakåtkompatibel förändring. Tillägget av implementeringar av gränssnittsfunktionens medlemmar gjorde det möjligt för utvecklare att uppgradera ett gränssnitt samtidigt som alla implementorer kunde åsidosätta implementeringen. Användare av gränssnittet kan acceptera implementeringen som en icke-icke-icke-bakåtkompatibel ändring. Men om deras krav skiljer sig åt kan de åsidosätta de angivna implementeringarna. slutkommentar
19.2 Gränssnittsdeklarationer
19.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 (§23), följt av en valfri uppsättning interface_modifier(§19.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 (§19.2.3), följt av en valfri interface_base specifikation (§19.2.4), följt av en valfri type_parameter_constraints_clausespecifikation (§15.2.5), följt av en interface_body (§19.3), eventuellt följt av semikolon.
En gränssnittsdeklaration får inte tillhandahålla 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).
19.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 (§24.2) är endast tillgänglig i osäker kod (§24).
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, internaloch 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, internaloch private modifierare), gäller reglerna i §15.2.2 .
19.2.3 Parameterlistor av varianttyp
19.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_parameter (',' variant_type_parameter)* '>'
    ;
variant_type_parameter
    : 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.
19.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.
19.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ᵢ
19.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.
Ett härlett gränssnitt kan deklarera nya medlemmar som döljer ärvda medlemmar (§7.7.2.3) som deklarerats i basgränssnitt eller uttryckligen implementera ärvda medlemmar (§19.6.2) som deklarerats i basgränssnitt.
interface_base
    : ':' interface_type_list
    ;
De explicita basgränssnitten kan vara konstruerade gränssnittstyper (§8.4, §19.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
privateellerinternalett gränssnitt i interface_base för ettpublicgrä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
IComboBoxför ärIControl,ITextBoxochIListBox. Med andra ordIComboBoxärver gränssnittet ovan medlemmarSetTextochSetItemssamtPaint.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ärverCombinemetoden efter att typparameternThar 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 (§19.2.3.2).
19.3 Gränssnittstext
Interface_body i ett gränssnitt definierar medlemmarna i gränssnittet.
interface_body
    : '{' interface_member_declaration* '}'
    ;
19.4 Gränssnittsmedlemmar
19.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
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;
Den här satsen utökar beskrivningen av medlemmar i klasser (§15.3) med begränsningar för gränssnitt. Gränssnittsmedlemmarna deklareras med hjälp av member_declarationmed följande ytterligare regler:
- En finalizer_declaration tillåts inte.
- Instanskonstruktorer , constructor_declarations, tillåts inte.
- Alla gränssnittsmedlemmar har implicit offentlig åtkomst; En explicit åtkomstmodifierare (§7.5.2) är dock tillåten förutom för statiska konstruktorer (§15.12).
- Modifieraren är underförstådd för gränssnittsfunktionsmedlemmar utan organ. Den abstractmodifieraren kan uttryckligen ges.
- En gränssnittsinstansfunktionsmedlem vars deklaration innehåller en brödtext är implicit virtualmedlem om inte modifierarensealedellerprivateanvänds. Modifierarenvirtualkan anges uttryckligen.
- En privateellersealeden funktionsmedlem i ett gränssnitt ska ha ett organ.
- En privatefunktionsmedlem får inte ha modifierarensealed.
- Ett härlett gränssnitt kan åsidosätta en abstrakt eller virtuell medlem som deklarerats i ett basgränssnitt.
- En uttryckligen implementerad funktionsmedlem får inte ha modifieraren sealed.
Vissa deklarationer, till exempel constant_declaration (§15.4) har inga begränsningar i 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 anses inte vara ett fel, men det resulterar i en varning (§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.
Obs! Medlemmarna i klassen
objectär inte strikt talande medlemmar i något gränssnitt (§19.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.
Exempel: Överväg ett gränssnitt
IAmed en implementering för en medlemMoch en egenskapP. En implementeringstypCtillhandahåller inte någon implementering för antingenMellerP. De måste nås via en referens vars kompileringstidstyp är ett gränssnitt som implicit kan konverteras tillIAellerIB. Dessa medlemmar hittas inte via medlemssökning på en variabel av typenC.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 } }I gränssnitten
IAochIBär medlemmenMtillgänglig direkt med namn. I metodenMainkan vi dock inte skrivac.M()ellerc.P, eftersom dessa namn inte visas. För att hitta dem behövs gjutningar till lämplig gränssnittstyp. Deklarationen avMiIBanvänder explicit gränssnittsimplementeringssyntax. Detta är nödvändigt för att den metoden ska åsidosätta den iIA. Modifierarenoverridekanske inte tillämpas på en funktionsmedlem. slutexempel
19.4.2 Gränssnittsfält
Den här satsen utökar beskrivningen av fält i klasserna §15.5 för fält som deklarerats i gränssnitt.
Gränssnittsfält deklareras med field_declaration(§15.5.1) med följande ytterligare regler:
- Det är ett kompileringsfel för field_declaration att deklarera ett instansfält.
Exempel: Följande program innehåller statiska medlemmar av olika slag:
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}"); } }De utdata som genereras är
static members initialized constant = 100, field = 0 static constructor has run constant = 100, field = 50slutexempel
Se §19.4.8 för information om allokering och initiering av statiska fält.
19.4.3 Gränssnittsmetoder
Denna sats utökar beskrivningen av metoder i klasserna §15.6 för metoder som deklareras i gränssnitt.
Gränssnittsmetoder deklareras med hjälp av method_declarations (§15.6)). Attributen, return_type, ref_return_type, identifierare och parameter_list för en gränssnittsmetoddeklaration har samma betydelse som för en metoddeklaration i en klass. Gränssnittsmetoder har följande ytterligare regler:
- method_modifier får inte innehålla - override.
- En metod vars brödtext är ett semikolon ( - ;) är- abstract;- abstractmodifieraren krävs inte, men tillåts.
- En gränssnittsmetoddeklaration som har en blocktext eller uttryckstext som en method_body är - virtual; modifieraren- virtualkrävs inte, men tillåts.
- En method_declaration får inte ha type_parameter_constraints_clauseom den inte också har en type_parameter_list. 
- Listan över krav för giltiga kombinationer av modifierare som anges för en klassmetod utökas enligt följande: - En statisk deklaration som inte är extern ska ha en blockkropp eller uttryckstext som en method_body.
- En virtuell deklaration som inte är extern ska ha en blocktext eller uttryckstext som en method_body.
- En privat deklaration som inte är extern ska ha en blocktext eller uttryckstext som en method_body.
- En förseglad deklaration som inte är extern ska ha en blockkropp eller uttryckstext som en method_body.
- En asynkron deklaration ska ha en blocktext eller uttryckstext som en method_body.
 
- Alla parametertyper för en gränssnittsmetod ska vara indatasäkra (§19.2.3.2) och returtypen ska vara antingen - voideller utgångssäker.
- Alla typer av utdata eller referensparametrar ska också vara utdatasäkra. - Obs! Utdataparametrar måste vara indatasäkra på grund av vanliga implementeringsbegränsningar. slutkommentar 
- Varje klasstypsbegränsning, villkor för gränssnittstyp och typparameterbegränsning för alla typparametrar i metoden ska 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
Tsom en typparameterbegränsningUinte ä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:
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>();Det här är faktiskt ett anrop till
C.M<E>. Men det samtalet kräver att härledasEfrånD, så typ säkerhet skulle brytas här.slutexempel
Obs! Se§19.4.2 för ett exempel som inte bara visar en statisk metod med en implementering, utan som den metoden kallas
Mainoch har rätt returtyp och signatur, är det också en startpunkt. slutkommentar
En virtuell metod med implementering som deklarerats i ett gränssnitt kan åsidosättas för att vara abstrakt i ett härlett gränssnitt. Detta kallas reabstraction.
Exempel:
interface IA { void M() { Console.WriteLine("IA.M"); } } interface IB: IA { abstract void IA.M(); // reabstraction of M }Detta är användbart i härledda gränssnitt där implementeringen av en metod är olämplig och en lämpligare implementering bör tillhandahållas av implementeringsklasser. slutexempel
19.4.4 Gränssnittsegenskaper
Den här satsen utökar beskrivningen av egenskaper i klasserna §15.7 för egenskaper som deklareras i gränssnitt.
Gränssnittsegenskaper deklareras med hjälp av property_declarations (§15.7.1) med följande ytterligare regler:
- property_modifier får inte innehålla - override.
- En explicit implementering av gränssnittsmedlemmar får inte innehålla någon accessor_modifier (§15.7.3). 
- Ett härlett gränssnitt kan uttryckligen implementera en abstrakt gränssnittsegenskap som deklarerats i ett basgränssnitt. - Obs! Eftersom ett gränssnitt inte kan innehålla instansfält kan en gränssnittsegenskap inte vara en automatisk instansegenskap, eftersom det skulle kräva deklaration av implicita dolda instansfält. slutkommentar 
- 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. 
- En gränssnittsmetoddeklaration som har en blocktext eller uttryckstext som en method_body är - virtual; modifieraren- virtualkrävs inte, men tillåts.
- En instans property_declaration som inte har någon implementering är - abstract;- abstractmodifieraren krävs inte, men tillåts. Det anses aldrig vara en automatiskt implementerad egenskap (§15.7.4).
19.4.5 Gränssnittshändelser
Denna sats utökar beskrivningen av händelser i klasserna §15.8 för händelser som deklarerats i gränssnitt.
Gränssnittshändelser deklareras med hjälp av event_declarations (§15.8.1), med följande ytterligare regler:
- 
              event_modifier får inte innehålla override.
- Ett härlett gränssnitt kan implementera en abstrakt gränssnittshändelse som deklareras i ett basgränssnitt (§15.8.5).
- Det är ett kompileringsfel för variable_declarators i en instans event_declaration innehålla alla variable_initializers.
- En instanshändelse med virtualellersealedmodifierare måste deklarera åtkomstpunkter. Det anses aldrig vara en automatiskt implementerad fältliknande händelse (§15.8.2).
- En instanshändelse med abstractmodifieraren får inte deklarera accessorer.
- Typen av gränssnittshändelse ska vara indatasäker.
19.4.6 Gränssnittsindexerare
Denna sats utökar beskrivningen av indexerare i klasserna §15.9 för indexerare som deklareras i gränssnitt.
Gränssnittsindexerare deklareras med hjälp av indexer_declarations (§15.9), med följande ytterligare regler:
- indexer_modifier får inte innehålla - override.
- En indexer_declaration som har en uttryckstext eller innehåller en accessor med en blocktext eller uttryckstext är - virtual;- virtualmodifieraren krävs inte, men tillåts.
- En indexer_declaration vars tillbehörskroppar är semikolon ( - ;) är- abstract;- abstractmodifieraren krävs inte, men tillåts.
- Alla parametertyper för en gränssnittsindexerare ska vara indatasäkra (§19.2.3.2). 
- Alla typer av utdata eller referensparametrar ska 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. 
19.4.7 Gränssnittsoperatorer
Denna klausul utökar beskrivningen av operator_declaration medlemmar i klasserna §15.10 för operatörer som deklarerats i gränssnitt.
En operator_declaration i ett gränssnitt är implementeringen (§19.1).
Det är ett kompileringsfel för ett gränssnitt för att deklarera en konverterings-, likhets- eller ojämlikhetsoperator.
19.4.8 Gränssnitt statiska konstruktorer
Den här satsen utökar beskrivningen av statiska konstruktorer i klasserna §15.12 för statiska konstruktorer som deklarerats i gränssnitt.
Den statiska konstruktorn för ett stängt (§8.4.3)-gränssnitt körs högst en gång i en viss programdomän. Körningen av en statisk konstruktor utlöses av den första av följande åtgärder som ska utföras inom en programdomän:
- Alla statiska medlemmar i gränssnittet refereras till.
- 
              MainInnan metoden anropas för ett gränssnitt som innehållerMainmetoden (§7.1) där körningen börjar.
- Gränssnittet tillhandahåller en implementering för en medlem och den implementeringen används som den mest specifika implementeringen (§19.4.10) för den medlemmen.
Obs! Om ingen av föregående åtgärder utförs kan det hända att den statiska konstruktorn för ett gränssnitt inte körs för ett program där instanser av typer som implementerar gränssnittet skapas och används. slutkommentar
Om du vill initiera en ny typ av stängt gränssnitt skapas först en ny uppsättning statiska fält för den specifika stängda typen. Vart och ett av de statiska fälten initieras till standardvärdet. Därefter körs de statiska fältinitierarna för dessa statiska fält. Slutligen körs den statiska konstruktorn.
Obs! Se§19.4.2 för ett exempel på användning av olika typer av statiska medlemmar (inklusive en Main-metod) som deklarerats i ett gränssnitt. slutkommentar
19.4.9 Gränssnitt kapslade typer
Den här satsen utökar beskrivningen av kapslade typer i klasserna §15.3.9 för kapslade typer som deklarerats i gränssnitt.
Det är ett fel att deklarera en klasstyp, structtyp eller uppräkningstyp inom omfånget för en typparameter som deklarerades med en variance_annotation (§19.2.3.1).
Exempel: Deklarationen
Cnedan är ett fel.interface IOuter<out T> { class C { } // error: class declaration within scope of variant type parameter 'T' }slutexempel
19.4.10 Det mest specifika genomförandet
Varje klass och struct ska ha en mest specifik implementering för varje virtuell medlem som deklareras i alla gränssnitt som implementeras av den typen bland de implementeringar som förekommer i typen eller dess direkta och indirekta gränssnitt. Den mest specifika implementeringen är en unik implementering som är mer specifik än alla andra implementeringar.
Obs! Den mest specifika implementeringsregeln säkerställer att en tvetydighet som uppstår vid arv av diamantgränssnitt uttryckligen löses av programmeraren vid den tidpunkt då konflikten inträffar. slutkommentar
För en typ T som är en struct eller en klass som implementerar gränssnitt I2 och I3, där I2 och I3 båda härleds direkt eller indirekt från ett gränssnitt I som deklarerar en medlem M, är den mest specifika implementeringen av M :
- Om Tdeklarerar en implementering avI.Mär implementeringen den mest specifika implementeringen.
- Om Tär en klass och en direkt eller indirekt basklass deklarerar en implementering avI.Mär den mest härledda basklassenTden mest specifika implementeringen.
- Annars, om I2ochI3är gränssnitt som implementeras avTochI3härleds frånI2antingen direkt eller indirekt,I3.Mär en mer specifik implementering änI2.M.
- Annars är varken I2.MellerI3.Mmer specifika och ett fel inträffar.
Exempel:
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(); }Den mest specifika implementeringsregeln säkerställer att en konflikt (dvs. en tvetydighet som uppstår till följd av diamantarv) uttryckligen löses av programmeraren vid den tidpunkt då konflikten uppstår. slutexempel
19.4.11 Åtkomst till gränssnittsmedlem
Gränssnittsmedlemmar nås via medlemsåtkomst (§12.8.7) och indexeraråtkomst (§12.8.12.4) uttryck i formuläret I.M och I[A], där I är en gränssnittstyp, M är en konstant, fält, metod, egenskap eller händelse av den gränssnittstypen och A är en indexerare argumentlista.
I en klass D, med direkt eller indirekt basklass B, där B direkt eller indirekt implementerar gränssnitt I och I definierar en metod M(), är uttrycket base.M() endast giltigt om base.M() statiskt (§12.3) binder till en implementering av M() i en klasstyp.
För gränssnitt som är strikt enkelarv (varje gränssnitt i arvskedjan har exakt noll eller ett direkt basgränssnitt), effekterna av medlemssökningen (§12.5), metodanrop (§12.8.1 0.2) och indexerarens åtkomstregler (§12.8.12.4) är exakt samma som för klasser och structs: 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 { 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 } }Det första påståendet orsakar ett kompileringsfel eftersom sökning av medlem (§12.5) av
CountiIListCounterär tvetydig. Som illustreras av exemplet löses tvetydigheten genom gjutningxtill 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.Addi §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.Fmedlemmen. Anropetd.F(1)väljerILeft.Fdärför , även omIBase.Fdet 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
IDerivedtillILefttillIBasedöljerIBase.Fär medlemmen också dold i åtkomstsökvägen frånIDerivedtillIRight.IBaseslutexempel
19.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.Paintoch det kvalificerade namnet på SetText ärITextBox.SetText. I exemplet ovan går det inte att referera tillPaintsomITextBox.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(); } }
SystemI namnområdet är bådeICloneable.CloneochSystem.ICloneable.Clonekvalificerade gränssnittsmedlemsnamn förClonemetoden.slutexempel
19.6 Gränssnittsimplementeringar
19.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.
En klass eller struct C som implementerar ett gränssnitt I måste tillhandahålla eller ärva en implementering för varje medlem som deklareras i I som C kan komma åt. Offentliga medlemmar I i kan definieras i offentliga medlemmar i C. Icke-offentliga medlemmar som deklareras i I som är tillgängliga i C kan definieras i C med explicit gränssnittsimplementering (§19.6.2).
En medlem i en härledd typ som uppfyller gränssnittsmappningen (§19.6.5) men som inte implementerar den matchande basgränssnittsmedlemmen introducerar en ny medlem. Detta inträffar när explicit gränssnittsimplementering krävs för att definiera gränssnittsmedlemmen.
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
TextBoxbådeIControlochITextBox.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, §19.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 skall uppfylla den unika regel som beskrivs i §19.6.3.
19.6.2 Explicita implementeringar av gränssnittsmedlemmar
I syfte att implementera gränssnitt kan en klass, struct eller ett gränssnitt 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. En klass eller struct som implementerar en icke-offentlig medlem i ett basgränssnitt måste deklarera en explicit implementering av gränssnittsmedlemmar. Ett gränssnitt som implementerar en medlem i ett basgränssnitt måste deklarera en explicit implementering av gränssnittsmedlem.
En härledd gränssnittsmedlem som uppfyller gränssnittsmappningen (§19.6.5) döljer basgränssnittsmedlemmen (§7.7.2). Kompilatorn ska utfärda en varning om inte new modifieraren finns.
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>.thisochIDictionary<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
Closemedlemsfunktion som har effekten att släppa filresursen och implementeraDisposemetoden för gränssnittet med explicit implementering avIDisposablegrä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 gränssnittsinstansmedlemimplementering kan bara nås via en gränssnittsinstans och refereras i så fall bara av dess medlemsnamn. En explicit implementering av statiska medlemmar i gränssnittet kan bara nås via gränssnittsnamnet.
Det är ett kompileringsfel för en explicit implementering av gränssnittsmedlemmar för att inkludera andra modifierare (§15.6) än extern eller async.
En explicit gränssnittsmetodimplementering ärver alla typparameterbegränsningar från gränssnittet.
En type_parameter_constraints_clause på en explicit gränssnittsmetodimplementering får endast bestå av de class eller structprimary_constraintsom tillämpas på type_parametersom är kända enligt de ärvda begränsningarna vara antingen referens- eller värdetyper. Alla typer av formuläret T? i signaturen för den explicita gränssnittsmetodimplementeringen, där T är en typparameter, tolkas på följande sätt:
- Om en classbegränsning har lagts till för typparameternTär denT?en referenstyp som kan ogiltigförklaras, annars
- Om det antingen inte finns någon ytterligare begränsning eller om en structbegränsning läggs till för typparameternTär detT?en nullbar värdetyp.
Exempel: Följande visar hur reglerna fungerar när typparametrar ingår:
#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 { } }Utan typparametervillkoret
where T : classkan inte basmetoden med referenstypad typparametern åsidosättas. slutexempel
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, struct eller ett gränssnitt att ha olika implementeringar av gränssnittsmedlemmar med samma signatur- och returtyp, vilket skulle vara omöjligt för en klass, struct eller ett gränssnitt att ha någon implementering på alla gränssnittsmedlemmar med samma signatur men med olika returtyper.
slutkommentar
För att en explicit implementering av gränssnittsmedlemmar ska vara giltig ska klassen, struct eller gränssnittet namnge ett gränssnitt i basklassen eller basgränssnittslistan som innehåller en medlem vars kvalificerade gränssnittsmedlemsnamn, typ, antal typparametrar och parametertyper exakt matchar dem för den explicita implementeringen av gränssnittsmedlemmen. 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.CompareToresulterar i ett kompileringsfel eftersomIComparableden inte finns med i basklasslistan förShapeoch 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.Clonei resulterar iEllipseett kompileringsfel eftersomICloneabledet 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
19.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 Lvara en lista över gränssnitt som har angetts direkt i en allmän klass-, struct- eller gränssnittsdeklarationC.
- Lägg till Li 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 Cskulle, efter att typargument har ersatts medL, orsaka två gränssnitt iLvara identiska, är deklarationen avCogiltig. Villkorsdeklarationer beaktas inte när du fastställer alla möjliga konstruerade typer.
Obs! I klassdeklarationen
Xovan består gränssnittslistanLavl<U>ochI<V>. Deklarationen är ogiltig eftersom en konstruerad typ medUochVsamma 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 Derived, eftersom Derived<int,int>' den effektivt implementerar I<int> om (§19.6.7).
19.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: objecteftersomobjectä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 (stringi 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, §19.6.2). slutkommentar
19.6.5 Gränssnittsmappning
En klass eller struct ska tillhandahålla implementeringar av alla abstrakta 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, gränssnitt eller struct , från och med C och upprepas för varje efterföljande basklass och implementerat Sgränssnitt Cför tills en matchning finns:
- Om Sinnehåller en förklaring av en explicit gränssnittsmedlemsimplementering som matcharIochM, är den här medlemmen implementeringen avI.M.
- Annars, om Sinnehå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, ett gränssnitt eller en structmedlem A en gränssnittsmedlem B när:
- 
              AochBär metoder, och namn, typ och parameterlistor förAochBär identiska.
- 
              AochBär egenskaper, namnet och typen av ochAär identiska ochBhar samma åtkomst somA(Btillåts ha ytterligare åtkomst om det inte är en explicit implementering avAgränssnittsmedlemmar).
- 
              AochBär händelser, och namnet och typen avAochBär identiska.
- 
              AochBär indexerare, typen och parameterlistorna för ochAär identiska ochBhar samma åtkomst somA(Btillåts ha ytterligare åtkomst om det inte är en explicit implementering avAgrä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.CloneiCblir implementering avCloneiICloneableeftersom explicita gränssnittmedlemsimplementeringar 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
PaintbådaIControlochIFormtillPaint-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
IControlnamngivna i basklasslistan, ärvdaIControlavITextBoxoch ärvdaIControlavIListBox. Det finns faktiskt ingen uppfattning om en separat identitet för dessa gränssnitt. Implementeringarna avITextBoxoch delar i stället samma implementering avIListBox, ochIControlanses helt enkelt implementera tre gränssnitt,ComboBox,IControlochITextBoxIListBox.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
FiClass1används iClass2'simplementeringen avInterface1.slutexempel
19.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() {...} }
Paintmetoden iTextBoxdöljerPaintmetoden iControl, men den ändrar inte mappningen tillControl.PaintIControl.Paint, och anrop tillPaintvia 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
Controlspecialisera implementeringen avIControl.Paintgenom attPaintControlåsidosätta metoden.slutexempel
19.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
Controlkartor tillIControl.PaintControl.IControl.Paintinte påverkar omimplementeringen iMyControl, som mapparIControl.PainttillMyControl.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
IMethodsiDerivedgränssnittsmetoderna tillDerived.F,Base.IMethods.G,Derived.IMethods.HochBase.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.FtillD.F.slutexempel
19.6.8 Abstrakta klasser och gränssnitt
Precis som en icke-abstrakt klass ska en abstrakt klass tillhandahålla implementeringar av alla abstrakta 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
IMethodskartorFochGabstrakta 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
Cför att åsidosättaFFochGG, vilket ger den faktiska implementeringen avIMethods.slutexempel
ECMA C# draft specification