Dela via


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, 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 .

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 och Z ä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 delegattyp Sᵢ,... Aₑ som konstruerats av en allmän typ S<Xᵢ, ... Xₑ> där minst en Aᵢ av följande finns:
    • Xᵢ är variant eller invariant och Aᵢ är inte säker på utdata.
    • Xᵢ är kontravariant eller invariant och Aᵢ ä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 delegattyp S<Aᵢ,... Aₑ> som konstruerats av en allmän typ S<Xᵢ, ... Xₑ> där minst en Aᵢ av följande finns:
    • Xᵢ är kovariant eller invariant och Aᵢ är indatasäkert.
    • Xᵢ är kontravariant eller invariant och Aᵢ ä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ån Aᵢ till Bᵢ
  • Xᵢ är kontravariant och det finns en implicit referens eller identitetskonvertering från Bᵢ till Aᵢ
  • Xᵢ är invariant och det finns en identitetskonvertering från Aᵢ till Bᵢ

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 eller internal ett gränssnitt i interface_base för ett public 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 är IControl, ITextBoxoch IListBox. Med andra ord IComboBox ärver gränssnittet ovan medlemmar SetText och SetItems samt Paint.

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 ärver Combine metoden efter att typparametern T har ersatts med string[,].

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 infrån , outoch ref.
  • 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 klassen object ä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änsning U 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ärledas E från D, 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 i IListCounter är tvetydig. Som illustreras av exemplet löses tvetydigheten genom gjutning x 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äljer n.Add(1.0)anropet IDouble.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 av ILeft.F medlemmen. Anropet d.F(1) väljer ILeft.Fdärför , även om IBase.F det inte verkar vara dolt i åtkomstsökvägen som leder genom IRight.

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 till ILeft till IBase döljer IBase.Fär medlemmen också dold i åtkomstsökvägen från IDerived till IRight .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 är IControl.Paint och det kvalificerade namnet på SetText är ITextBox.SetText. I exemplet ovan går det inte att referera till Paint som ITextBox.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åde ICloneable.Clone och System.ICloneable.Clone kvalificerade gränssnittsmedlemsnamn för Clone 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åde IControl och ITextBox.

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 och IDictionary<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 implementera Dispose metoden för gränssnittet med explicit implementering av IDisposable 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 eftersom IComparable den inte finns med i basklasslistan för Shape och inte är ett basgränssnitt för ICloneable. På samma sätt, i deklarationerna

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
}

class Ellipse : Shape
{
    object ICloneable.Clone() {...} // invalid
}

deklarationen av ICloneable.Clone i resulterar i Ellipse ett kompileringsfel eftersom ICloneable det inte uttryckligen anges i basklasslistan för Ellipse.

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, inte ITextBox.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änssnittsdeklaration C.
  • Lägg till L i alla basgränssnitt för gränssnitten som redan finns i L.
  • 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 med L, orsaka två gränssnitt i L vara identiska, är deklarationen av C ogiltig. Villkorsdeklarationer beaktas inte när du fastställer alla möjliga konstruerade typer.

Obs! I klassdeklarationen X ovan består gränssnittslistan L av l<U> och I<V>. Deklarationen är ogiltig eftersom en konstruerad typ med U och V 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> implementerar I<object,C,string>.F<T>implicit . I det här fallet C.F<T> krävs inte (eller tillåts) för att ange villkoret T: object eftersom object är en implicit begränsning för alla typparametrar. Metoden C.G<T> implementerar I<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 metoden C.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 implementera I<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 matchar I och M, är den här medlemmen implementeringen av I.M.
  • Annars, om S innehåller en deklaration av en icke-statisk offentlig medlem som matchar M, är den här medlemmen implementeringen av I.M. Om fler än en medlem matchar är det ospecificerat vilken medlem som är implementeringen av I.M. Den här situationen kan bara inträffa om S ä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 och B är metoder, och namn, typ och parameterlistor för A och B är identiska.
  • Aoch B är egenskaper, namnet och typen av och A är identiska och B har samma åtkomst som A (B tillåts ha ytterligare åtkomst om det inte är en explicit implementering av A gränssnittsmedlemmar).
  • A och B är händelser, och namnet och typen av A och B är identiska.
  • Aoch B är indexerare, typen och parameterlistorna för och A är identiska och B har samma åtkomst som A (B tillåts ha ytterligare åtkomst om det inte är en explicit implementering av A 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 av C blir genomförandet av Clone 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åda IControl och IForm till Paint -metoden i Page. 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, ärvda IControl av ITextBoxoch ärvda IControl av IListBox. Det finns faktiskt ingen uppfattning om en separat identitet för dessa gränssnitt. Implementeringarna av ITextBoxoch delar i stället samma implementering av IListBox, och IControl anses helt enkelt implementera tre gränssnitt, ComboBox, IControloch ITextBoxIListBox .

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 i Class1 används i Class2's implementeringen av Interface1.

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 i TextBox döljer Paint metoden i Control, men den ändrar inte mappningen till Control.PaintIControl.Paint , och anrop till Paint via klassinstanser och gränssnittsinstanser får följande effekter

Control c = new Control();
TextBox t = new TextBox();
IControl ic = c;
IControl it = t;
c.Paint();  // invokes Control.Paint();
t.Paint();  // invokes TextBox.Paint();
ic.Paint(); // invokes Control.Paint();
it.Paint(); // invokes Control.Paint();

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 av IControl.Paint genom att PaintControl å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 till IControl.PaintControl.IControl.Paint inte påverkar omimplementeringen i MyControl, som mappar IControl.Paint till MyControl.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 i Derived gränssnittsmetoderna till Derived.F, Base.IMethods.G, Derived.IMethods.Hoch Base.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 omimplementeringar IBase, mappning IBase.F till D.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 kartor F och G abstrakta metoder åsidosättas i icke-abstrakta klasser som härleds från C.

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ätta FF och GG, vilket ger den faktiska implementeringen av IMethods.

slutexempel