Sdílet prostřednictvím


18 Rozhraní

18.1 Obecné

Rozhraní definuje kontrakt. Třída nebo struktura, která implementuje rozhraní, musí dodržovat svou smlouvu. Rozhraní může dědit z více základních rozhraní a třída nebo struktura může implementovat více rozhraní.

Rozhraní mohou obsahovat metody, vlastnosti, události a indexery. Samotné rozhraní neposkytuje implementace pro členy, které deklaruje. Rozhraní pouze určuje členy, které mají být dodány třídami nebo strukturami, které implementují rozhraní.

18.2 Deklarace rozhraní

18.2.1 Obecné

Interface_declaration je type_declaration (§14.7), který deklaruje nový typ rozhraní.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Interface_declaration se skládá z volitelné sady atributů (§22), následované volitelnou sadou interface_modifiers (§18.2.2), následovanou volitelným částečným modifikátorem (§15.2.7), následovaným klíčovým slovem interface a identifikátorem, který označuje rozhraní, následovaný volitelnou specifikací variant_type_parameter_list (§18.2.3), následovanou volitelným interface_base specifikace (§18.2.4), po níž následuje volitelná specifikace type_parameter_constraints_clause(§15.2.5), za kterou následuje interface_body (§18.3), případně následuje středník.

Prohlášení o rozhraní nesmí poskytovat type_parameter_constraints_clauses, pokud rovněž nenabídá variant_type_parameter_list.

Deklarace rozhraní, která poskytuje variant_type_parameter_list je obecná deklarace rozhraní. Kromě toho je jakékoli rozhraní vnořené uvnitř obecné deklarace třídy nebo obecná deklarace struktury sama o sobě obecná deklarace rozhraní, protože argumenty typu obsahujícího typu musí být zadány k vytvoření typu konstrukce (§8.4).

18.2.2 Modifikátory rozhraní

Interface_declaration může volitelně obsahovat posloupnost modifikátorů rozhraní:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) je k dispozici pouze v nebezpečném kódu (§23).

Jedná se o chybu v době kompilace, aby se stejný modifikátor zobrazoval vícekrát v deklaraci rozhraní.

new Modifikátor je povolen pouze u rozhraní definovaných v rámci třídy. Určuje, že rozhraní skryje zděděný člen se stejným názvem, jak je popsáno v §15.3.5.

publicModifikátory , , protectedinternala private řídí přístupnost rozhraní. V závislosti na kontextu, ve kterém se deklarace rozhraní vyskytuje, mohou být povoleny pouze některé z těchto modifikátorů (§7.5.2). Pokud prohlášení o částečném typu (§15.2.7) zahrnuje specifikaci přístupnosti (prostřednictvím public, protected, internala private modifikátorů), použijí se pravidla v §15.2.2 .

18.2.3 Seznamy parametrů variantního typu

18.2.3.1 Obecné

Seznamy parametrů variantního typu mohou nastat pouze u typů rozhraní a delegátů. Rozdíl od běžných type_parameter_lists je volitelný variance_annotation u každého parametru typu.

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'
    ;

Pokud je outrozptylová poznámka , parametr typu se říká, že je kovariantní. Pokud je inrozptylová poznámka , parametr typu se říká, že je kontravariantní. Pokud neexistuje žádná odchylka anotace, parametr typu je invariantní.

Příklad: V následujícím příkladu:

interface C<out X, in Y, Z>
{
    X M(Y y);
    Z P { get; set; }
}

X je kovariantní, Y je kontravariantní a Z je invariantní.

end example

Je-li obecné rozhraní deklarováno ve více částech (§15.2.3), musí každé částečné prohlášení určit stejnou odchylku pro každý parametr typu.

18.2.3.2 Bezpečnost rozptylu

Výskyt poznámek rozptylu v seznamu parametrů typu typu omezuje místa, kde se můžou v deklaraci typu vyskytovat typy.

Typ T je nebezpečný pro výstup, pokud platí jedna z následujících možností:

  • T je parametr kontravariantního typu.
  • T je typ pole s nebezpečným typem výstupního prvku.
  • T je typ Sᵢ,... Aₑ rozhraní nebo delegáta vytvořený z obecného typu S<Xᵢ, ... Xₑ> , kde platí alespoň pro jednu Aᵢ z následujících možností:
    • Xᵢ je kovariantní nebo invariantní a Aᵢ je nebezpečný pro výstup.
    • Xᵢ je kontravariantní nebo invariantní a Aᵢ je nebezpečný.

Typ T je nebezpečný , pokud platí jedna z následujících možností:

  • T je kovariantní parametr typu.
  • T je typ pole se vstupním nebezpečným typem elementu.
  • T je typ S<Aᵢ,... Aₑ> rozhraní nebo delegáta vytvořený z obecného typu S<Xᵢ, ... Xₑ> , kde platí alespoň pro jednu Aᵢ z následujících možností:
    • Xᵢ je kovariantní nebo invariantní a Aᵢ je nebezpečný pro vstup.
    • Xᵢ je kontravariantní nebo invariantní a Aᵢ je nebezpečný pro výstup.

Intuitivně je ve výstupní pozici zakázán nebezpečný typ výstupu a vstupní nebezpečný typ je zakázán ve vstupní pozici.

Typ je bezpečný pro výstup, pokud není nebezpečný pro výstup a je bezpečný pro vstup, pokud není nebezpečný pro vstup.

18.2.3.3 Převod rozptylu

Účelem poznámek odchylek je poskytnout více lenientních (ale přesto bezpečných) převodů na typy rozhraní a delegátů. Za tímto účelem se definice implicitního (§10.2) a explicitního převodu (§10.3) používají pojem variance-convertibility, který je definován takto:

Typ T<Aᵢ, ..., Aᵥ> je variance-konvertibilní na typ T<Bᵢ, ..., Bᵥ> , pokud T je buď rozhraní, nebo delegát typ deklarován s parametry T<Xᵢ, ..., Xᵥ>variant typu , a pro každý parametr Xᵢ typu varianty jeden z následujících blokování:

  • Xᵢ je kovariantní a implicitní odkaz nebo převod identity existuje z Aᵢ do Bᵢ
  • Xᵢ je kontravariantní a implicitní odkaz nebo převod identity existuje z Bᵢ do Aᵢ
  • Xᵢ je invariantní a převod identity existuje z Aᵢ do Bᵢ

18.2.4 Základní rozhraní

Rozhraní může dědit z nuly nebo více typů rozhraní, které se nazývají explicitní základní rozhraní rozhraní. Pokud má rozhraní jedno nebo více explicitních základních rozhraní, pak v deklaraci tohoto rozhraní následuje identifikátor rozhraní dvojtečka a čárkami oddělený seznam základních typů rozhraní.

interface_base
    : ':' interface_type_list
    ;

Explicitní základní rozhraní lze vytvořit typy rozhraní (§8.4, §18.2). Základní rozhraní nemůže být parametr typu sám, i když může zahrnovat parametry typu, které jsou v oboru.

U typu vytvořeného rozhraní jsou explicitní základní rozhraní tvořena explicitními deklaracemi základního rozhraní pro deklaraci obecného typu a nahrazením každé type_parameter v deklaraci základního rozhraní odpovídající type_argument konstruovaného typu.

Explicitní základní rozhraní rozhraní musí být alespoň tak přístupné jako samotné rozhraní (§7.5.5).

Poznámka: Jedná se například o chybu v době kompilace, která určuje private rozhraní internal v interface_basepublic rozhraní. koncová poznámka

Jedná se o chybu v době kompilace, která rozhraní přímo nebo nepřímo dědí ze sebe.

Základní rozhraní rozhraní jsou explicitní základní rozhraní a jejich základní rozhraní. Jinými slovy, sada základních rozhraní je kompletní tranzitivní uzavření explicitních základních rozhraní, jejich explicitních základních rozhraní atd. Rozhraní dědí všechny členy jeho základních rozhraní.

Příklad: V následujícím kódu

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

základní rozhraní IComboBox jsou IControl, ITextBoxa IListBox. Jinými slovy, IComboBox výše uvedené rozhraní dědí členy SetText a SetItems také Paint.

end example

Členové zděděné z vytvořeného obecného typu se dědí po nahrazení typu. To znamená, že všechny typy složek v členu mají parametry typu deklarace základní třídy nahrazeny odpovídajícími argumenty typu použitými ve specifikaci class_base.

Příklad: V následujícím kódu

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

rozhraní IDerived dědí metodu Combine po nahrazení parametru Tstring[,]typu .

end example

Třída nebo struktura, která implementuje rozhraní také implicitně implementuje všechny základní rozhraní rozhraní.

Zpracování rozhraní na více částech částečné deklarace rozhraní (§15.2.7) je dále popsáno v §15.2.4.3.

Každé základní rozhraní rozhraní musí být výstupní-bezpečné (§18.2.3.2).2).

18.3 Tělo rozhraní

Interface_body rozhraní definuje členy rozhraní.

interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 Členy rozhraní

18.4.1 Obecné

Členy rozhraní jsou členy zděděné ze základních rozhraní a členy deklarované samotným rozhraním.

interface_member_declaration
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

Deklarace rozhraní deklaruje nula nebo více členů. Členy rozhraní musí být metody, vlastnosti, události nebo indexery. Rozhraní nemůže obsahovat konstanty, pole, operátory, konstruktory instancí, finalizátory nebo typy ani rozhraní nesmí obsahovat statické členy jakéhokoli druhu.

Všichni členové rozhraní mají implicitně veřejný přístup. Jedná se o chybu v době kompilace pro deklarace členů rozhraní, aby zahrnovaly všechny modifikátory.

Interface_declaration vytvoří nový prostor deklarace (§7.3) a parametry typu a interface_member_declarationokamžitě obsažené v interface_declaration zavést do tohoto prostoru deklarace nové členy. Následující pravidla platí pro interface_member_declarations:

  • Název parametru typu v variant_type_parameter_list deklarace rozhraní se liší od názvů všech ostatních parametrů typu ve stejném variant_type_parameter_list a musí se lišit od názvů všech členů rozhraní.
  • Název metody se liší od názvů všech vlastností a událostí deklarovaných ve stejném rozhraní. Kromě toho se podpis (§7.6) metody liší od podpisů všech ostatních metod deklarovaných ve stejném rozhraní a dvě metody deklarované ve stejném rozhraní nesmí obsahovat podpisy, které se liší pouze in, outa ref.
  • Název vlastnosti nebo události se liší od názvů všech ostatních členů deklarovaných ve stejném rozhraní.
  • Podpis indexeru se liší od podpisů všech ostatních indexerů deklarovaných ve stejném rozhraní.

Zděděné členy rozhraní nejsou konkrétně součástí prostoru deklarace rozhraní. Rozhraní je tedy povoleno deklarovat člen se stejným názvem nebo podpisem jako zděděný člen. V takovém případě se odvozený člen rozhraní říká, že skryje člen základního rozhraní. Skrytí zděděného člena se nepovažuje za chybu, ale způsobí, že kompilátor vydá upozornění. K potlačení upozornění musí deklarace odvozeného členu rozhraní obsahovat new modifikátor, který indikuje, že odvozený člen je určen ke skrytí základního členu. Toto téma je popsáno dále v §7.7.2.3.

new Pokud je modifikátor součástí deklarace, která neskryje zděděný člen, zobrazí se upozornění. Toto upozornění je potlačeno odebráním modifikátoru new .

Poznámka: Členy třídy object nejsou, přísně řečeno, členy jakéhokoli rozhraní (§18.4). Členy třídy object jsou však k dispozici prostřednictvím vyhledávání členů v libovolném typu rozhraní (§12.5). koncová poznámka

Sada členů rozhraní deklarovaných ve více částech (§15.2.7) je sjednocení členů deklarovaných v každé části. Subjekty všech částí prohlášení o rozhraní mají stejný prostor prohlášení (§7.3) a rozsah každého člena (§7.7) se vztahuje na subjekty všech částí.

18.4.2 Metody rozhraní

Metody rozhraní se deklarují pomocí interface_method_declarations:

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* ';'
    ;

Atributy, return_type, ref_return_type, identifikátor a parameter_list deklarace metody rozhraní mají stejný význam jako deklarace metody ve třídě (§15.6). Deklarace metody rozhraní není povolena k určení těla metody, a proto deklarace vždy končí středníkem.

Všechny typy parametrů metody rozhraní musí být vstupní-bezpečné (§18.2.3.2) a návratový typ musí být buď void nebo výstupní. Kromě toho musí být všechny typy výstupních nebo referenčních parametrů také bezpečné pro výstup.

Poznámka: Výstupní parametry musí být kvůli běžným omezením implementace bezpečné pro vstup. koncová poznámka

Kromě toho musí být každé omezení typu třídy, omezení typu rozhraní a omezení parametru typu pro všechny parametry typu metody vstupně-bezpečné.

Kromě toho musí být každé omezení typu třídy, omezení typu rozhraní a omezení parametru typu pro jakýkoli parametr typu metody vstupně-bezpečné.

Tato pravidla zajišťují, aby jakékoli kovariantní nebo kontravariantní použití rozhraní zůstalo beze zbytku typu.

Příklad:

interface I<out T>
{
    void M<U>() where U : T;     // Error
}

je špatně vytvořený, protože použití T jako omezení U parametru typu není bezpečné pro vstup.

Pokud by toto omezení nebylo zavedeno, bylo by možné porušení bezpečnosti typů následujícím způsobem:

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

I<B> b = new C();
b.M<E>();

To je vlastně volání C.M<E>. Toto volání však vyžaduje, aby E bylo odvozeno z D, takže bezpečnost typů by zde byla porušena.

end example

18.4.3 Vlastnosti rozhraní

Vlastnosti rozhraní se deklarují pomocí interface_property_declarations:

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' ';'
    ;

Atributy, typ a identifikátor deklarace vlastnosti rozhraní mají stejný význam jako vlastnosti deklarace vlastnosti ve třídě (§15.7).

Přístupové objekty deklarace vlastnosti rozhraní odpovídají přistupujícím objektům deklarace vlastnosti třídy (§15.7.3), s výjimkou toho, že accessor_body musí být vždy středníkem. Proto přístupové objekty jednoduše označují, zda je vlastnost jen pro čtení, jen pro čtení nebo jen pro zápis.

Typ vlastnosti rozhraní musí být výstupní-bezpečný, pokud je k dispozici přístupové objekty get, a musí být vstupní-bezpečné, pokud je k dispozici nastavené příslušenství.

18.4.4 Události rozhraní

Události rozhraní se deklarují pomocí interface_event_declarations:

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

Atributy, typ a identifikátor deklarace události rozhraní mají stejný význam jako atributy deklarace události ve třídě (§15.8).

Typ události rozhraní musí být bezpečný pro vstup.

18.4.5 Indexery rozhraní

Indexery rozhraní se deklarují pomocí interface_indexer_declarations:

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

Atributy, typ a parameter_list deklarace indexeru rozhraní mají stejný význam jako atributy deklarace indexeru ve třídě (§15.9).

Přístupové objekty deklarace indexeru rozhraní odpovídají přístupovým objektům deklarace indexeru třídy (§15.9), s výjimkou toho, že accessor_body musí být vždy středníkem. Proto přístupové objekty jednoduše označují, jestli je indexer jen pro čtení, jen pro čtení nebo jen pro zápis.

Všechny typy parametrů indexeru rozhraní musí být vstupní-bezpečné (§18.2.3.2). Kromě toho musí být všechny typy výstupních nebo referenčních parametrů také bezpečné pro výstup.

Poznámka: Výstupní parametry musí být kvůli běžným omezením implementace bezpečné pro vstup. koncová poznámka

Typindexho zařízení musí být výstupově bezpečný, pokud je k dispozici přístupový objekt get, a musí být bezpečný pro vstup, pokud je k dispozici nastavený přístupový objekt.

18.4.6 Přístup člena rozhraní

K členům rozhraní se přistupuje prostřednictvím přístupu členů (§12.8.7) a indexeru (§12.8.12.3) výrazů formuláře I.M a I[A], kde I je typ rozhraní, M je metoda, vlastnost nebo událost tohoto typu rozhraní a A je seznam argumentů indexeru.

Pro rozhraní, která jsou výhradně jednoduchou dědičností (každé rozhraní v řetězci dědičnosti má přesně nula nebo jedno přímé základní rozhraní), účinky vyhledávání členů (§12.5), vyvolání metody (§12.8.10.2) a přístup indexerů (§12.8.12.3) jsou přesně stejná jako u tříd a struktur: Více odvozené členy skryjí méně odvozené členy se stejným názvem nebo podpisem. U rozhraní s více dědičnostmi ale může dojít k nejednoznačnostem, když dva nebo více nesouvisejících základních rozhraní deklarují členy se stejným názvem nebo podpisem. Tato dílčí ukázka ukazuje několik příkladů, z nichž některé vedou k nejednoznačnostem a jiným, které ne. Ve všech případech lze explicitní přetypování použít k vyřešení nejednoznačností.

Příklad: V následujícím kódu

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
    }
}

první dva příkazy způsobují chyby v době kompilace, protože vyhledávání členů (§12.5) Count je IListCounter nejednoznačné. Jak je znázorněno v příkladu, nejednoznačnost se přeloží přetypováním x na odpovídající základní typ rozhraní. Takové přetypování nemají žádné náklady za běhu – pouze se skládají z zobrazení instance jako méně odvozeného typu v době kompilace.

end example

Příklad: V následujícím kódu

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
    }
}

vyvolání n.Add(1) vybere IInteger.Add použitím pravidel řešení přetížení §12.6.4. Podobně vyvolání n.Add(1.0) vybere IDouble.Add. Při vložení explicitních přetypování existuje pouze jedna metoda kandidáta, a proto není nejednoznačnost.

end example

Příklad: V následujícím kódu

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
    }
}

člen IBase.F je skrytý ILeft.F členem. Vyvolání d.F(1) tedy vybere ILeft.F, i když IBase.F se zdá, že není skryta v přístupové cestě, která vede .IRight

Intuitivní pravidlo pro skrytí v rozhraních s více dědičností je jednoduché: Pokud je člen skrytý v jakékoli přístupové cestě, je skrytý ve všech přístupových cestách. Vzhledem k tomu, že přístupová cesta od IDerived k ILeftIBase skrytí IBase.F, člen je také skrytý v přístupové cestě od IDerived do IRightIBase.

end example

18.5 Kvalifikované názvy členů rozhraní

Člen rozhraní se někdy označuje jeho kvalifikovaným názvem člena rozhraní. Kvalifikovaný název člena rozhraní se skládá z názvu rozhraní, ve kterém je člen deklarován, následovaný tečkou následovanou názvem člena. Kvalifikovaný název člena odkazuje na rozhraní, ve kterém je člen deklarován.

Příklad: Vzhledem k deklaracím

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

kvalifikovaný název Paint je IControl.Paint a kvalifikovaný název SetText je ITextBox.SetText. V předchozím příkladu není možné odkazovat na Paint .ITextBox.Paint

end example

Pokud je rozhraní součástí oboru názvů, může kvalifikovaný název člena rozhraní obsahovat název oboru názvů.

Příklad:

namespace System
{
    public interface ICloneable
    {
        object Clone();
    }
}

System V rámci oboru názvů jsou ICloneable.CloneSystem.ICloneable.Clone kvalifikované názvy členů rozhraní pro metoduClone.

end example

18.6 Implementace rozhraní

18.6.1 Obecné

Rozhraní mohou být implementována třídami a strukturami. Chcete-li označit, že třída nebo struktura přímo implementuje rozhraní, je rozhraní zahrnuto v seznamu základních tříd třídy nebo struktury.

Příklad:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

end example

Třída nebo struktura, která přímo implementuje rozhraní také implicitně implementuje všechna základní rozhraní rozhraní. To platí i v případě, že třída nebo struktura explicitně nevypisuje všechna základní rozhraní v seznamu základních tříd.

Příklad:

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Zde třída TextBox implementuje jak IControl a ITextBox.

end example

Když třída C přímo implementuje rozhraní, všechny třídy odvozené z C také implementují rozhraní implicitně.

Základní rozhraní zadaná v deklaraci třídy mohou být vytvořena typy rozhraní (§8.4, §18.2).

Příklad: Následující kód ukazuje, jak může třída implementovat vytvořené typy rozhraní:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

end example

Základní rozhraní obecné deklarace třídy splňují pravidlo jedinečnosti popsané v §18.6.3.

18.6.2 Explicitní implementace členů rozhraní

Pro účely implementace rozhraní může třída nebo struktura deklarovat explicitní implementace členů rozhraní. Explicitní implementace člena rozhraní je metoda, vlastnost, událost nebo indexer deklarace, která odkazuje na kvalifikovaný název člena rozhraní.

Příklad:

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) {...}
}

Tady IDictionary<int,T>.this jsou IDictionary<int,T>.Add explicitní implementace členů rozhraní.

end example

Příklad: V některých případech nemusí být název člena rozhraní vhodný pro implementační třídu, v takovém případě může být člen rozhraní implementován pomocí explicitní implementace člena rozhraní. Třída implementující abstrakci souboru, například by pravděpodobně implementovala členovou Close funkci, která má vliv na uvolnění prostředku souboru, a implementovala Dispose metodu IDisposable rozhraní pomocí explicitní implementace člena rozhraní:

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);
    }
}

end example

Explicitní implementaci člena rozhraní není možné získat přístup prostřednictvím jeho kvalifikovaného názvu člena rozhraní při vyvolání metody, přístupu k vlastnostem, přístupu k událostem nebo přístupu indexeru. K explicitní implementaci člena rozhraní lze přistupovat pouze prostřednictvím instance rozhraní a v tomto případě se na to odkazuje jednoduše jeho názvem člena.

Jedná se o chybu v době kompilace pro explicitní implementaci člena rozhraní, aby zahrnovaly jakékoli modifikátory (§15.6) jiné než extern nebo async.

Jedná se o chybu v době kompilace pro explicitní implementaci metody rozhraní, která zahrnuje type_parameter_constraints_clauses. Omezení pro implementaci obecné explicitní metody rozhraní jsou zděděna z metody rozhraní.

Poznámka: Explicitní implementace členů rozhraní mají jiné vlastnosti přístupnosti než ostatní členy. Vzhledem k tomu, že explicitní implementace členů rozhraní nejsou nikdy přístupné prostřednictvím kvalifikovaného názvu člena rozhraní při vyvolání metody nebo přístupu k vlastnosti, jsou ve smyslu privátní. Vzhledem k tomu, že k nim lze přistupovat prostřednictvím rozhraní, jsou ve smyslu také jako veřejné jako rozhraní, ve kterém jsou deklarovány. Explicitní implementace členů rozhraní slouží ke dvěma primárním účelům:

  • Vzhledem k tomu, že implementace členů explicitního rozhraní nejsou přístupné prostřednictvím instancí třídy nebo struktury, umožňují, aby implementace rozhraní byly vyloučeny z veřejného rozhraní třídy nebo struktury. To je zvlášť užitečné, když třída nebo struktura implementuje interní rozhraní, které není zajímavé pro příjemce této třídy nebo struktury.
  • Explicitní implementace členů rozhraní umožňují nejednoznačnost členů rozhraní se stejným podpisem. Bez explicitních implementací členů rozhraní by nebylo možné, aby třída nebo struktura měly různé implementace členů rozhraní se stejným podpisem a návratovým typem, stejně jako by nebylo možné, aby třída nebo struktura měla jakoukoli implementaci u všech členů rozhraní se stejným podpisem, ale s různými návratovými typy.

koncová poznámka

Aby byla implementace explicitního člena rozhraní platná, třída nebo struktura pojmenuje rozhraní v seznamu základních tříd, které obsahuje člena, jehož kvalifikovaný název člena rozhraní, typ, počet parametrů typu a typy parametrů přesně odpovídají typům implementace explicitního člena rozhraní. Pokud má člen funkce rozhraní pole parametrů, je povolen odpovídající parametr přidružené explicitní implementace člena rozhraní, ale není vyžadován, aby params měl modifikátor. Pokud člen funkce rozhraní nemá pole parametrů, přidružená implementace explicitního členu rozhraní nebude mít pole parametrů.

Příklad: Proto v následující třídě

class Shape : ICloneable
{
    object ICloneable.Clone() {...}
    int IComparable.CompareTo(object other) {...} // invalid
}

deklarace IComparable.CompareTo výsledků při chybě v době kompilace, protože IComparable není uvedena v seznamu Shape základních tříd a není základním rozhraním ICloneable. Stejně tak v deklaracích

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

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

deklarace ICloneable.Clone v Ellipse důsledku toho způsobí chybu v době kompilace, protože ICloneable není explicitně uvedena v seznamu Ellipsezákladních tříd .

end example

Kvalifikovaný název člena rozhraní implementace explicitního člena rozhraní odkazuje na rozhraní, ve kterém byl člen deklarován.

Příklad: Proto v deklaracích

interface IControl
{
    void Paint();
}

interface ITextBox : IControl
{
    void SetText(string text);
}

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

Explicitní implementace člena rozhraní Malování musí být zapsána jako IControl.Paint, nikoli ITextBox.Paint.

end example

18.6.3 Jedinečnost implementovaných rozhraní

Rozhraní implementovaná obecnou deklarací typu zůstanou jedinečná pro všechny možné konstruované typy. Bez tohoto pravidla by nebylo možné určit správnou metodu volání určitých konstruovaných typů.

Příklad: Předpokládejme, že byla povolena deklarace obecné třídy takto:

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() {...}
}

Bylo by to povoleno, nebylo by možné určit, který kód se má provést v následujícím případě:

I<int> x = new X<int, int>();
x.F();

end example

Chcete-li zjistit, zda je seznam rozhraní deklarace obecného typu platný, jsou provedeny následující kroky:

  • Pojďme L být seznam rozhraní přímo určených v obecné třídě, struktuře nebo deklaraci Crozhraní .
  • Přidat do L všech základních rozhraní rozhraní již v L.
  • Odeberte všechny duplicity z L.
  • Pokud by byl vytvořen jakýkoli možný vytvořený typ C , po nahrazení argumentů typu do L, způsobit, že dvě rozhraní budou L identické, pak deklarace C je neplatná. Při určování všech možných konstruovaných typů se deklarace omezení nepovažují.

Poznámka: V deklaraci X třídy výše se seznam L rozhraní skládá a l<U>I<V>. Deklarace je neplatná, protože jakýkoli vytvořený typ se stejným typem UV by způsobil, že tato dvě rozhraní budou identické typy. koncová poznámka

Rozhraní zadaná na různých úrovních dědičnosti je možné sjednotit:

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() {...}
}

Tento kód je platný, i když Derived<U,V> implementuje obojí I<U> i I<V>. Kód

I<int> x = new Derived<int, int>();
x.F();

vyvolá metodu v Derived, protože Derived<int,int>' účinně znovu implementuje I<int> (§18.6.7).

18.6.4 Implementace obecných metod

Pokud obecná metoda implicitně implementuje metodu rozhraní, omezení pro každý parametr typu metody musí být v obou deklaracích ekvivalentní (po nahrazení parametrů typu rozhraní příslušnými argumenty typu), kde jsou parametry typu metody identifikovány pořadovými pozicemi, zleva doprava.

Příklad: V následujícím kódu:

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
}

metoda C.F<T> implicitně implementuje I<object,C,string>.F<T>. V tomto případě není nutné (ani povoleno) určit omezeníC.F<T>, T: object protože object je implicitní omezení pro všechny parametry typu. Metoda C.G<T> implicitně implementuje I<object,C,string>.G<T> , protože omezení odpovídají omezením v rozhraní, po nahrazení parametrů typu rozhraní odpovídajícími argumenty typu. Omezení metody C.H<T> je chyba, protože zapečetěné typy (string v tomto případě) nelze použít jako omezení. Vynechání omezení by také bylo chybou, protože omezení implementace implicitních metod rozhraní se vyžadují ke shodě. Proto není možné implicitně implementovat I<object,C,string>.H<T>. Tuto metodu rozhraní lze implementovat pouze pomocí explicitní implementace člena rozhraní:

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);
    }
}

V tomto případě explicitní implementace člena rozhraní vyvolá veřejnou metodu s přísně slabšími omezeními. Přiřazení z t na s je platné, protože T dědí omezení T: string, i když toto omezení není ve zdrojovém kódu vyjádřitelné. end example

Poznámka: Pokud obecná metoda explicitně implementuje metodu rozhraní bez omezení prováděcí metody (§15.7.1, §18.6.2). koncová poznámka

18.6.5 Mapování rozhraní

Třída nebo struktura musí poskytovat implementace všech členů rozhraní, která jsou uvedena v seznamu základních tříd třídy nebo struktury. Proces vyhledání implementací členů rozhraní v implementační třídě nebo struktuře se označuje jako mapování rozhraní.

Mapování rozhraní pro třídu nebo strukturu C vyhledá implementaci pro každý člen každého rozhraní zadaného v seznamu Czákladních tříd . Implementace konkrétního člena I.Mrozhraní , kde I je rozhraní, ve kterém je člen M deklarován, je určeno prozkoumáním každé třídy nebo struktury S, počínaje C a opakováním pro každou po sobě jdoucí základní třídu C, dokud se shoda nenachází:

  • Obsahuje-li S deklaraci explicitní implementace členu rozhraní, která odpovídá I , a Mpak tento člen je implementace I.M.
  • V opačném případě, pokud S obsahuje deklaraci nestatického veřejného členu, který odpovídá M, pak tento člen je implementace I.M. Pokud se shoduje více než jeden člen, není určeno, který člen je implementací I.M. K této situaci může dojít pouze v případě S , že je vytvořený typ, kde dva členy deklarované v obecném typu mají různé podpisy, ale argumenty typu činí jejich podpisy identické.

K chybě v době kompilace dochází v případě, že implementace nelze nalézt pro všechny členy všech rozhraní zadaných v seznamu Czákladních tříd . Členy rozhraní zahrnují ty členy, které jsou zděděny ze základních rozhraní.

Členové konstruovaného typu rozhraní se považují za všechny parametry typu nahrazené odpovídajícími argumenty typu, jak je uvedeno v §15.3.3.

Příklad: Například vzhledem k deklaraci obecného rozhraní:

interface I<T>
{
    T F(int x, T[,] y);
    T this[int y] { get; }
}

vytvořené rozhraní I<string[]> má členy:

string[] F(int x, string[,][] y);
string[] this[int y] { get; }

end example

Pro účely mapování rozhraní odpovídá člen A třídy nebo struktury člen B rozhraní, pokud:

  • A a B jsou metody a název, typ a seznamy A parametrů a B jsou identické.
  • A a B jsou vlastnosti, název a typ A a B jsou identické a A mají stejné přístupové objekty jako B (A je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní).
  • A a B jsou události a název a typ A a B jsou identické.
  • A a B jsou indexery, seznamy A typů a parametrů a B jsou identické a A mají stejné přístupové objekty jako B (A je povoleno mít další přístupové objekty, pokud to není explicitní implementace člena rozhraní).

Významné důsledky algoritmu mapování rozhraní jsou:

  • Explicitní implementace členů rozhraní mají přednost před ostatními členy ve stejné třídě nebo struktuře při určování třídy nebo člena struktury, který implementuje člen rozhraní.
  • Neveřejné ani statické členy se neúčastní mapování rozhraní.

Příklad: V následujícím kódu

interface ICloneable
{
    object Clone();
}

class C : ICloneable
{
    object ICloneable.Clone() {...}
    public object Clone() {...}
}

Element ICloneable.Clone ve struktuře C se stane implementací Clone v ICloneable, protože implementace explicitních členů rozhraní mají přednost před ostatními členy.

end example

Pokud třída nebo struktura implementuje dvě nebo více rozhraní obsahující člen se stejným názvem, typem a typy parametrů, je možné mapovat každý z těchto členů rozhraní na jednu třídu nebo člen struktury.

Příklad:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

class Page : IControl, IForm
{
    public void Paint() {...}
}

Zde jsou Paint metody obou IControl a IForm jsou mapovány na metodu Paint v Page. Je samozřejmě také možné mít samostatné explicitní implementace členů rozhraní pro tyto dvě metody.

end example

Pokud třída nebo struktura implementuje rozhraní, které obsahuje skryté členy, může být nutné některé členy implementovat prostřednictvím explicitních implementací členů rozhraní.

Příklad:

interface IBase
{
    int P { get; }
}

interface IDerived : IBase
{
    new int P();
}

Implementace tohoto rozhraní by vyžadovala aspoň jednu explicitní implementaci člena rozhraní a vyžadovala by jednu z následujících forem.

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() {...}
}

end example

Když třída implementuje více rozhraní, která mají stejné základní rozhraní, může existovat pouze jedna implementace základního rozhraní.

Příklad: V následujícím kódu

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) {...}
}

není možné mít samostatné implementace pro pojmenované IControl v seznamu základních tříd, IControl zděděné a ITextBoxIControl zděděné .IListBox Vskutku neexistuje pojem samostatné identity pro tato rozhraní. Spíše implementace ITextBoxa IListBox sdílejí stejnou implementaci IControl, a ComboBox je jednoduše považována za implementaci tří rozhraní, IControl, ITextBoxa IListBox.

end example

Členové základní třídy se účastní mapování rozhraní.

Příklad: V následujícím kódu

interface Interface1
{
    void F();
}

class Class1
{
    public void F() {}
    public void G() {}
}

class Class2 : Class1, Interface1
{
    public new void G() {}
}

metoda FClass1 je použita při Class2's provádění Interface1.

end example

18.6.6 Dědičnost implementace rozhraní

Třída dědí všechny implementace rozhraní poskytované základními třídami.

Bez explicitní opětovné implementace rozhraní nemůže odvozená třída žádným způsobem změnit mapování rozhraní, které dědí ze svých základních tříd.

Příklad: V deklaracích

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public void Paint() {...}
}

class TextBox : Control
{
    public new void Paint() {...}
}

metoda Paint skryje TextBox metodu Paint v Control, ale nemění mapování Control.Paint na IControl.Paint, a volání Paint prostřednictvím instancí třídy a rozhraní instance budou mít následující účinky

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();

end example

Pokud je však metoda rozhraní mapována na virtuální metodu ve třídě, je možné odvozené třídy přepsat virtuální metodu a změnit implementaci rozhraní.

Příklad: Přepsání výše uvedených deklarací na

interface IControl
{
    void Paint();
}

class Control : IControl
{
    public virtual void Paint() {...}
}

class TextBox : Control
{
    public override void Paint() {...}
}

nyní budou pozorovány následující účinky.

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();

end example

Vzhledem k tomu, že explicitní implementace členů rozhraní nelze deklarovat virtuální, není možné přepsat explicitní implementaci člena rozhraní. Je však naprosto platné pro explicitní implementaci člena rozhraní volat jinou metodu a že jiná metoda může být deklarována virtuální, aby bylo možné odvozené třídy přepsat.

Příklad:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Třídy odvozené z Control mohou specializovat implementaci IControl.Paint přepsáním PaintControl metody.

end example

18.6.7 Opětovné implementace rozhraní

Třída, která dědí implementaci rozhraní, je povoleno znovu implementovat rozhraní zahrnutím do seznamu základních tříd.

Opětovná implementace rozhraní se řídí přesně stejnými pravidly mapování rozhraní jako počáteční implementace rozhraní. Zděděné mapování rozhraní tedy nemá žádný vliv na mapování rozhraní vytvořené pro opětovné provedení rozhraní.

Příklad: V deklaracích

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() {...}
}

class MyControl : Control, IControl
{
    public void Paint() {}
}

skutečnost, že Control mapování IControl.Paint na nemá vliv na Control.IControl.Paint re-implementaci v MyControl, která se mapuje IControl.Paint na MyControl.Paint.

end example

Zděděné deklarace veřejného členu a zděděné deklarace členů explicitního rozhraní se účastní procesu mapování rozhraní pro znovu implementovaná rozhraní.

Příklad:

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() {}
}

Zde implementace IMethods v Derived mapách metod rozhraní na Derived.F, Base.IMethods.G, Derived.IMethods.Ha Base.I.

end example

Když třída implementuje rozhraní, implicitně také implementuje všechna základní rozhraní rozhraní. Podobně je opětovná implementace rozhraní také implicitně re-implementace všech základních rozhraní rozhraní.

Příklad:

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() {...}
}

Zde se opětovná implementace IDerived také znovu implementuje IBase, mapování IBase.F na D.F.

end example

18.6.8 Abstraktní třídy a rozhraní

Stejně jako abstraktní třída poskytuje abstraktní třída implementaci všech členů rozhraní, která jsou uvedena v seznamu základních tříd třídy. Abstraktní třída však může mapovat metody rozhraní na abstraktní metody.

Příklad:

interface IMethods
{
    void F();
    void G();
}

abstract class C : IMethods
{
    public abstract void F();
    public abstract void G();
    }

Zde provádění IMethods map F a G na abstraktní metody, které se přepíše v ne abstraktních třídách, které jsou odvozeny .C

end example

Explicitní implementace členů rozhraní nemohou být abstraktní, ale explicitní implementace členů rozhraní jsou samozřejmě povoleno volat abstraktní metody.

Příklad:

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();
}

V této části by se k přepsání C vyžadovaly ne abstraktní třídy, které jsou odvozenyFF, a GGtím by poskytovaly skutečnou implementaci IMethods.

end example