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.
public
Modifikátory , , protected
internal
a 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
, internal
a 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 out
rozptylová poznámka , parametr typu se říká, že je kovariantní. Pokud je in
rozptylová 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í aZ
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 typSᵢ,... Aₑ
rozhraní nebo delegáta vytvořený z obecného typuS<Xᵢ, ... Xₑ>
, kde platí alespoň pro jednuAᵢ
z následujících možností:-
Xᵢ
je kovariantní nebo invariantní aAᵢ
je nebezpečný pro výstup. -
Xᵢ
je kontravariantní nebo invariantní aAᵢ
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 typS<Aᵢ,... Aₑ>
rozhraní nebo delegáta vytvořený z obecného typuS<Xᵢ, ... Xₑ>
, kde platí alespoň pro jednuAᵢ
z následujících možností:-
Xᵢ
je kovariantní nebo invariantní aAᵢ
je nebezpečný pro vstup. -
Xᵢ
je kontravariantní nebo invariantní aAᵢ
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 zAᵢ
doBᵢ
-
Xᵢ
je kontravariantní a implicitní odkaz nebo převod identity existuje zBᵢ
doAᵢ
-
Xᵢ
je invariantní a převod identity existuje zAᵢ
doBᵢ
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
jsouIControl
,ITextBox
aIListBox
. Jinými slovy,IComboBox
výše uvedené rozhraní dědí členySetText
aSetItems
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í metoduCombine
po nahrazení parametruT
string[,]
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
,out
aref
. - 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řídyobject
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, abyE
bylo odvozeno zD
, 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
jeIListCounter
nejednoznačné. Jak je znázorněno v příkladu, nejednoznačnost se přeloží přetypovánímx
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)
vybereIInteger.Add
použitím pravidel řešení přetížení §12.6.4. Podobně vyvolánín.Add(1.0)
vybereIDouble.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 vybereILeft.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
kILeft
IBase
skrytíIBase.F
, člen je také skrytý v přístupové cestě odIDerived
doIRight
IBase
.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
jeIControl.Paint
a kvalifikovaný název SetText jeITextBox.SetText
. V předchozím příkladu není možné odkazovat naPaint
.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ů jsouICloneable.Clone
System.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 jakIControl
aITextBox
.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
jsouIDictionary<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 implementovalaDispose
metoduIDisposable
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žeIComparable
není uvedena v seznamuShape
základních tříd a není základním rozhranímICloneable
. Stejně tak v deklaracíchclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
deklarace
ICloneable.Clone
vEllipse
důsledku toho způsobí chybu v době kompilace, protožeICloneable
není explicitně uvedena v seznamuEllipse
zá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
, nikoliITextBox.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 deklaraciC
rozhraní . - Přidat do
L
všech základních rozhraní rozhraní již vL
. - Odeberte všechny duplicity z
L
. - Pokud by byl vytvořen jakýkoli možný vytvořený typ
C
, po nahrazení argumentů typu doL
, způsobit, že dvě rozhraní budouL
identické, pak deklaraceC
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 seznamL
rozhraní skládá al<U>
I<V>
. Deklarace je neplatná, protože jakýkoli vytvořený typ se stejným typemU
V
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ě implementujeI<object,C,string>.F<T>
. V tomto případě není nutné (ani povoleno) určit omezeníC.F<T>
,T: object
protožeobject
je implicitní omezení pro všechny parametry typu. MetodaC.G<T>
implicitně implementujeI<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í metodyC.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ě implementovatI<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 C
základních tříd . Implementace konkrétního člena I.M
rozhraní , 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
, aM
pak tento člen je implementaceI.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 implementaceI.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 C
zá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
aB
jsou metody a název, typ a seznamyA
parametrů aB
jsou identické. -
A
aB
jsou vlastnosti, název a typA
aB
jsou identické aA
mají stejné přístupové objekty jakoB
(A
je povoleno mít další přístupové objekty, pokud se nejedná o explicitní implementaci člena rozhraní). -
A
aB
jsou události a název a typA
aB
jsou identické. -
A
aB
jsou indexery, seznamyA
typů a parametrů aB
jsou identické aA
mají stejné přístupové objekty jakoB
(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řeC
se stane implementacíClone
vICloneable
, 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 obouIControl
aIForm
jsou mapovány na metoduPaint
vPage
. 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é aITextBox
IControl
zděděné .IListBox
Vskutku neexistuje pojem samostatné identity pro tato rozhraní. Spíše implementaceITextBox
aIListBox
sdílejí stejnou implementaciIControl
, aComboBox
je jednoduše považována za implementaci tří rozhraní,IControl
,ITextBox
aIListBox
.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
F
Class1
je použita přiClass2'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
skryjeTextBox
metoduPaint
vControl
, ale nemění mapováníControl.Paint
naIControl.Paint
, a voláníPaint
prostřednictvím instancí třídy a rozhraní instance budou mít následující účinkyControl 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 implementaciIControl.Paint
přepsánímPaintControl
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 naControl.IControl.Paint
re-implementaci vMyControl
, která se mapujeIControl.Paint
naMyControl.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
vDerived
mapách metod rozhraní naDerived.F
,Base.IMethods.G
,Derived.IMethods.H
aBase.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 implementujeIBase
, mapováníIBase.F
naD.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
mapF
aG
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
, aGG
tím by poskytovaly skutečnou implementaciIMethods
.end example
ECMA C# draft specification