18 Interfejsy
18.1 Ogólne
Interfejs definiuje kontrakt. Klasa lub struktura, która implementuje interfejs, jest zgodna z jego umową. Interfejs może dziedziczyć z wielu interfejsów podstawowych, a klasa lub struktura może implementować wiele interfejsów.
Interfejsy mogą zawierać metody, właściwości, zdarzenia i indeksatory. Sam interfejs nie zapewnia implementacji dla elementów członkowskich, które deklaruje. Interfejs jedynie określa elementy członkowskie, które mają być dostarczane przez klasy lub struktury, które implementują interfejs.
18.2 Deklaracje interfejsu
18.2.1 Ogólne
Interface_declaration to type_declaration (§14.7), który deklaruje nowy typ interfejsu.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
Interface_declaration składa się z opcjonalnego zestawu atrybutów (§22), po którym następuje opcjonalny zestaw interface_modifiers (§18.2.2), a następnie opcjonalny modyfikator częściowy (§15.2.7), po którym następuje słowo kluczowe interface
i identyfikator, który nazywa interfejs, a następnie opcjonalną specyfikację variant_type_parameter_list (§18.2.3), a następnie opcjonalny interface_base specyfikacja (§18.2.4), po której następuje opcjonalna specyfikacja type_parameter_constraints_clause(§15.2.5), a następnie interface_body (§18.3), po której następuje średnik.
Deklaracja interfejsu nie dostarcza type_parameter_constraints_clauses, chyba że dostarcza również variant_type_parameter_list.
Deklaracja interfejsu dostarczająca variant_type_parameter_list jest ogólną deklaracją interfejsu. Ponadto każdy interfejs zagnieżdżony wewnątrz deklaracji klasy ogólnej lub deklaracji struktury ogólnej jest samą deklaracją interfejsu ogólnego, ponieważ argumenty typu dla typu zawierającego muszą zostać dostarczone w celu utworzenia skonstruowanego typu (§8.4).
18.2.2 Modyfikatory interfejsu
Interface_declaration może opcjonalnie zawierać sekwencję modyfikatorów interfejsu:
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) jest dostępny tylko w niebezpiecznym kodzie (§23).
Jest to błąd czasu kompilacji dla tego samego modyfikatora, który pojawia się wiele razy w deklaracji interfejsu.
Modyfikator new
jest dozwolony tylko w interfejsach zdefiniowanych w klasie. Określa, że interfejs ukrywa dziedziczony element członkowski o tej samej nazwie, zgodnie z opisem w §15.3.5.
Modyfikatory public
, protected
internal
i private
kontrolują dostępność interfejsu. W zależności od kontekstu, w którym występuje deklaracja interfejsu, tylko niektóre z tych modyfikatorów mogą być dozwolone (§7.5.2). Gdy deklaracja typu częściowego (§15.2.7) zawiera specyfikację ułatwień dostępu (za pośrednictwem public
modyfikatorów , protected
, internal
i private
), obowiązują reguły w §15.2.2 .
18.2.3 Listy parametrów typu wariantu
18.2.3.1 Ogólne
Listy parametrów typu wariantu mogą występować tylko w typach interfejsu i delegatów. Różnica w porównaniu ze zwykłymi type_parameter_lists jest opcjonalnym variance_annotation dla każdego 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'
;
Jeśli adnotacja wariancji to out
, parametr typu mówi się, że jest kowariantny. Jeśli adnotacja wariancji to in
, parametr typu mówi się, że jest kontrawariantny. Jeśli nie ma adnotacji wariancji, parametr typu jest mówi się, że jest niezmienny.
Przykład: w następujących kwestiach:
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
jest kowariantny,Y
jest kontrawariantny iZ
jest niezmienny.przykład końcowy
Jeśli interfejs ogólny jest zadeklarowany w wielu częściach (§15.2.3), każda deklaracja częściowa określa tę samą wariancję dla każdego parametru typu.
18.2.3.2 Bezpieczeństwo wariancji
Wystąpienie adnotacji wariancji na liście parametrów typu ogranicza miejsca, w których typy mogą występować w deklaracji typu.
Typ T jest niebezpieczny dla danych wyjściowych, jeśli jedna z następujących blokad:
T
jest kontrawariantnym parametrem typuT
jest typem tablicy z typem elementu output-niebezpiecznegoT
to interfejs lub typSᵢ,... Aₑ
delegata skonstruowany z typuS<Xᵢ, ... Xₑ>
ogólnego, w którym co najmniej jedenAᵢ
z następujących blokad:Xᵢ
jest kowariantny lub niezmienny iAᵢ
jest niebezpieczny dla danych wyjściowych.Xᵢ
jest kontrawariantny lub niezmienny iAᵢ
jest niebezpieczny dla danych wejściowych.
Typ T jest niebezpieczny dla danych wejściowych, jeśli jedna z następujących blokad:
T
jest kowariantnym parametrem typuT
jest typem tablicy z typem elementu input-niebezpiecznegoT
to interfejs lub typS<Aᵢ,... Aₑ>
delegata skonstruowany z typuS<Xᵢ, ... Xₑ>
ogólnego, w którym co najmniej jedenAᵢ
z następujących blokad:Xᵢ
jest kowariantny lub niezmienny iAᵢ
jest niebezpieczny dla danych wejściowych.Xᵢ
jest kontrawariantny lub niezmienny iAᵢ
jest niebezpieczny dla danych wyjściowych.
Intuicyjnie typ niebezpieczny dla danych wyjściowych jest zabroniony w pozycji wyjściowej, a typ wejściowy-niebezpieczny jest zabroniony w pozycji wejściowej.
Typ jest bezpieczny dla danych wyjściowych, jeśli nie jest niebezpieczny dla danych wyjściowych, a jeśli nie jest niebezpieczny dla danych wejściowych.
Konwersja wariancji 18.2.3.3
Celem adnotacji wariancji jest zapewnienie bardziej łagodnych (ale nadal bezpiecznych typów) konwersji na interfejs i typy delegatów. W tym celu definicje niejawnej (§10.2) i jawne konwersje (§10.3) korzystają z pojęcia niezmienności, która jest zdefiniowana w następujący sposób:
Typ T<Aᵢ, ..., Aᵥ>
to wariancja-kabriolet do typu T<Bᵢ, ..., Bᵥ>
, jeśli T
jest interfejsem lub typem delegata zadeklarowanym z parametrami T<Xᵢ, ..., Xᵥ>
typu wariantu , a dla każdego parametru typu wariantu Xᵢ
jeden z następujących blokad:
Xᵢ
jest kowariantny, a niejawna odwołanie lub konwersja tożsamości istnieje zAᵢ
doBᵢ
Xᵢ
jest kontrawariantny, a niejawna odwołanie lub konwersja tożsamości istnieje zBᵢ
doAᵢ
Xᵢ
jest niezmienna, a konwersja tożsamości istnieje zAᵢ
doBᵢ
18.2.4 Interfejsy podstawowe
Interfejs może dziedziczyć z zera lub większej liczby typów interfejsów, które są nazywane jawnymi interfejsami podstawowymi interfejsu. Gdy interfejs ma co najmniej jeden jawny interfejs podstawowy, to w deklaracji tego interfejsu identyfikator interfejsu następuje dwukropek i rozdzielana przecinkami lista typów interfejsów podstawowych.
interface_base
: ':' interface_type_list
;
Jawne interfejsy podstawowe mogą być konstruowane typy interfejsów (§8.4, §18.2). Interfejs podstawowy nie może być parametrem typu samodzielnie, ale może obejmować parametry typu, które znajdują się w zakresie.
W przypadku skonstruowanego typu interfejsu jawne interfejsy podstawowe są tworzone przez przyjmowanie jawnych deklaracji interfejsu podstawowego w deklaracji typu ogólnego i podstawianie dla każdego type_parameter w deklaracji interfejsu podstawowego, odpowiadających type_argument typu skonstruowanego.
Jawne interfejsy podstawowe interfejsu są co najmniej tak dostępne, jak sam interfejs (§7.5.5).
Uwaga: na przykład jest to błąd
private
czasu kompilacji określający interfejs lubinternal
w interface_base interfejsupublic
. notatka końcowa
Jest to błąd czasu kompilacji interfejsu do bezpośredniego lub pośrednio dziedziczonego z samego siebie.
Podstawowe interfejsy interfejsu to jawne interfejsy podstawowe i ich podstawowe interfejsy. Innymi słowy, zestaw interfejsów podstawowych jest kompletnym przechodnim zamknięciem jawnych interfejsów bazowych, ich jawnymi interfejsami podstawowymi itd. Interfejs dziedziczy wszystkie elementy członkowskie interfejsów podstawowych.
Przykład: w poniższym kodzie
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
podstawowe interfejsy to
IComboBox
IControl
,ITextBox
iIListBox
. Innymi słowy,IComboBox
powyższy interfejs dziedziczy elementy członkowskieSetText
,SetItems
a takżePaint
.przykład końcowy
Składowe dziedziczone z skonstruowanego typu ogólnego są dziedziczone po podstawieniu typu. Oznacza to, że wszystkie typy składowe w składowej mają parametry typu deklaracji klasy bazowej zastąpione odpowiednimi argumentami typu używanymi w specyfikacji class_base .
Przykład: w poniższym kodzie
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
interfejs
IDerived
dziedziczy metodęCombine
po zastąpieniu parametru type ciągiemstring[,]
T
.przykład końcowy
Klasa lub struktura, która implementuje interfejs, również niejawnie implementuje wszystkie interfejsy podstawowe interfejsu.
Obsługa interfejsów w wielu częściach części częściowej deklaracji interfejsu (§15.2.7) jest omówiona dalej w §15.2.4.3.
Każdy podstawowy interfejs interfejsu jest bezpieczny dla danych wyjściowych (§18.2.3.2).
Treść interfejsu 18.3
Interface_body interfejsu definiuje elementy członkowskie interfejsu.
interface_body
: '{' interface_member_declaration* '}'
;
18.4 Elementy członkowskie interfejsu
18.4.1 Ogólne
Elementy członkowskie interfejsu są elementami członkowskimi dziedziczone z interfejsów podstawowych i składowych zadeklarowanych przez sam interfejs.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
Deklaracja interfejsu deklaruje zero lub więcej elementów członkowskich. Składowe interfejsu są metodami, właściwościami, zdarzeniami lub indeksatorami. Interfejs nie może zawierać stałych, pól, operatorów, konstruktorów wystąpień, finalizatorów ani typów, ani interfejsu nie może zawierać statycznych elementów członkowskich jakiegokolwiek rodzaju.
Wszystkie elementy członkowskie interfejsu niejawnie mają dostęp publiczny. Jest to błąd czasu kompilacji deklaracji składowych interfejsu w celu uwzględnienia wszelkich modyfikatorów.
Interface_declaration tworzy nową przestrzeń deklaracji (§7.3) oraz parametry typu i interface_member_declaration natychmiast zawarte przez interface_declaration wprowadzać nowe elementy członkowskie do tej przestrzeni deklaracji. Następujące reguły mają zastosowanie do interface_member_declarations:
- Nazwa parametru typu w variant_type_parameter_list deklaracji interfejsu różni się od nazw wszystkich innych parametrów typu w tym samym variant_type_parameter_list i różni się od nazw wszystkich elementów członkowskich interfejsu.
- Nazwa metody różni się od nazw wszystkich właściwości i zdarzeń zadeklarowanych w tym samym interfejsie. Ponadto podpis metody (§7.6) różni się od podpisów wszystkich innych metod zadeklarowanych w tym samym interfejsie, a dwie metody zadeklarowane w tym samym interfejsie nie mają podpisów, które różnią się wyłącznie od
in
,out
iref
. - Nazwa właściwości lub zdarzenia różni się od nazw wszystkich innych elementów członkowskich zadeklarowanych w tym samym interfejsie.
- Podpis indeksatora różni się od podpisów wszystkich innych indeksatorów zadeklarowanych w tym samym interfejsie.
Dziedziczone elementy członkowskie interfejsu nie są częścią przestrzeni deklaracji interfejsu. W związku z tym interfejs może zadeklarować element członkowski o tej samej nazwie lub podpisie co dziedziczony element członkowski. W takim przypadku element członkowski interfejsu pochodnego jest wyświetlany w celu ukrycia podstawowego elementu członkowskiego interfejsu. Ukrycie dziedziczonego elementu członkowskiego nie jest traktowane jako błąd, ale powoduje, że kompilator wyda ostrzeżenie. Aby pominąć ostrzeżenie, deklaracja elementu członkowskiego interfejsu pochodnego zawiera new
modyfikator wskazujący, że pochodny element członkowski ma na celu ukrycie elementu członkowskiego podstawowego. Ten temat został omówiony dalej w §7.7.2.3.
new
Jeśli modyfikator jest uwzględniony w deklaracji, która nie ukrywa dziedziczonego elementu członkowskiego, ostrzeżenie zostanie wydane dla tego efektu. To ostrzeżenie jest pomijane przez usunięcie new
modyfikatora.
Uwaga: Składowe w klasie
object
nie są ściśle rzecz biorąc członkami żadnego interfejsu (§18.4). Jednak składowe w klasieobject
są dostępne za pośrednictwem wyszukiwania składowego w dowolnym typie interfejsu (§12.5). notatka końcowa
Zestaw elementów członkowskich interfejsu zadeklarowanego w wielu częściach (§15.2.7) jest związkiem elementów członkowskich zadeklarowanych w każdej części. Elementy wszystkich części deklaracji interfejsu współdzielą to samo miejsce deklaracji (§7.3), a zakres każdego elementu członkowskiego (§7.7) rozciąga się na elementy wszystkich części.
18.4.2 Metody interfejsu
Metody interfejsu są deklarowane przy użyciu 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* ';'
;
Atrybuty, return_type, ref_return_type, identyfikator i parameter_list deklaracji metody interfejsu mają takie samo znaczenie jak te deklaracji metody w klasie (§15.6). Deklaracja metody interfejsu nie może określać treści metody, a deklaracja zawsze kończy się średnikiem.
Wszystkie typy parametrów metody interfejsu powinny być bezpieczne dla danych wejściowych (§18.2.3.2), a zwracany typ musi być bezpieczny void
lub wyjściowy. Ponadto wszystkie typy parametrów wyjściowych lub referencyjnych również są bezpieczne dla danych wyjściowych.
Uwaga: Parametry wyjściowe muszą być bezpieczne dla danych wejściowych ze względu na typowe ograniczenia implementacji. notatka końcowa
Ponadto każde ograniczenie typu klasy, ograniczenie typu interfejsu i ograniczenie parametru typu dla dowolnego typu parametrów metody jest bezpieczne dla danych wejściowych.
Ponadto każde ograniczenie typu klasy, ograniczenie typu interfejsu i ograniczenie parametru typu dla dowolnego parametru typu metody jest bezpieczne dla danych wejściowych.
Te reguły zapewniają, że wszelkie kowariantne lub kontrawariantne użycie interfejsu pozostaje bezpieczne.
Przykład:
interface I<out T> { void M<U>() where U : T; // Error }
jest źle sformułowany, ponieważ użycie jako ograniczenia
U
parametruT
typu nie jest bezpieczne dla danych wejściowych.Gdyby to ograniczenie nie zostało wprowadzone, byłoby możliwe naruszenie bezpieczeństwa typu w następujący sposób:
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
Jest to faktycznie wywołanie metody
C.M<E>
. Jednak to wywołanie wymaga, abyE
pochodziło zD
klasy , więc bezpieczeństwo typu byłoby naruszone tutaj.przykład końcowy
Właściwości interfejsu 18.4.3
Właściwości interfejsu są deklarowane przy użyciu 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' ';'
;
Atrybuty, typ i identyfikator deklaracji właściwości interfejsu mają takie samo znaczenie jak atrybuty deklaracji właściwości w klasie (§15.7).
Metody dostępu deklaracji właściwości interfejsu odpowiadają metodom dostępu deklaracji właściwości klasy (§15.7.3), z wyjątkiem tego, że accessor_body zawsze jest średnikiem. W związku z tym metody dostępu po prostu wskazują, czy właściwość jest tylko do odczytu i zapisu, tylko do odczytu, czy tylko do zapisu.
Typ właściwości interfejsu musi być bezpieczny w przypadku uzyskania dostępu i musi być bezpieczny dla danych wejściowych, jeśli istnieje zestaw akcesoriów.
Zdarzenia interfejsu 18.4.4
Zdarzenia interfejsu są deklarowane przy użyciu interface_event_declarations:
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
Atrybuty, typ i identyfikator deklaracji zdarzenia interfejsu mają takie samo znaczenie jak atrybuty deklaracji zdarzeń w klasie (§15.8).
Typ zdarzenia interfejsu musi być bezpieczny dla danych wejściowych.
Indeksatory interfejsu 18.4.5
Indeksatory interfejsu są deklarowane przy użyciu 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 '}'
;
Atrybuty, typ i parameter_list deklaracji indeksatora interfejsu mają takie samo znaczenie jak atrybuty deklaracji indeksatora w klasie (§15.9).
Metody dostępu deklaracji indeksatora interfejsu odpowiadają metodom dostępu deklaracji indeksatora klasy (§15.9), z wyjątkiem tego, że accessor_body zawsze jest średnikiem. W związku z tym metody dostępu po prostu wskazują, czy indeksator jest tylko do odczytu i zapisu, tylko do odczytu, czy tylko do zapisu.
Wszystkie typy parametrów indeksatora interfejsu są bezpieczne dla danych wejściowych (§18.2.3.2). Ponadto wszystkie typy parametrów wyjściowych lub referencyjnych również są bezpieczne dla danych wyjściowych.
Uwaga: Parametry wyjściowe muszą być bezpieczne dla danych wejściowych ze względu na typowe ograniczenia implementacji. notatka końcowa
Typ indeksatora interfejsu musi być bezpieczny w przypadku uzyskania dostępu i musi być bezpieczny dla danych wejściowych, jeśli istnieje zestaw akcesoriów.
18.4.6 Dostęp do składowych interfejsu
Dostęp do elementów członkowskich jest uzyskiwany za pośrednictwem dostępu do składowych (§12.8.7) i dostępu indeksatora (§12.8.11.3) wyrażeń formularza I.M
i I[A]
, gdzie I
jest typem interfejsu, jest metodą, M
właściwością lub zdarzeniem tego typu interfejsu i A
jest listą argumentów indeksatora.
W przypadku interfejsów, które są ściśle pojedynczym dziedziczeniem (każdy interfejs w łańcuchu dziedziczenia ma dokładnie zero lub jeden bezpośredni interfejs podstawowy), efekty wyszukiwania składowych (§12.5), wywołanie metody (§12.8.9.2) i reguły dostępu indeksatora (§12.8.11.3) są dokładnie takie same jak w przypadku klas i struktur: Więcej pochodnych składowych ukrywa mniej pochodnych składowych o tej samej nazwie lub podpisie. Jednak w przypadku interfejsów wielokrotnego dziedziczenia niejednoznaczności mogą wystąpić, gdy co najmniej dwa niepowiązane interfejsy podstawowe deklarują elementy członkowskie o tej samej nazwie lub podpisie. W tym podklasie przedstawiono kilka przykładów, z których niektóre prowadzą do niejednoznaczności i innych. We wszystkich przypadkach jawne rzutowania mogą służyć do rozwiązywania niejednoznaczności.
Przykład: w poniższym kodzie
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 } }
pierwsze dwie instrukcje powodują błędy czasu kompilacji, ponieważ odnośnik składowy (§12.5)
Count
elementu inIListCounter
jest niejednoznaczny. Jak pokazano w przykładzie, niejednoznaczność jest rozpoznawana przez rzutowaniex
do odpowiedniego podstawowego typu interfejsu. Takie rzutowania nie mają kosztów czasu wykonywania — składają się tylko z wyświetlania wystąpienia jako mniej pochodnego typu w czasie kompilacji.przykład końcowy
Przykład: w poniższym kodzie
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 } }
wybór wywołania
n.Add(1)
IInteger.Add
przez zastosowanie reguł rozwiązywania przeciążenia §12.6.4. Podobnie wywołanien.Add(1.0)
wybieraIDouble.Add
wartość . W przypadku wstawiania jawnych rzutów istnieje tylko jedna metoda kandydata, a tym samym niejednoznaczność.przykład końcowy
Przykład: w poniższym kodzie
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 } }
element
IBase.F
członkowski jest ukryty przez członkaILeft.F
. Wywołanied.F(1)
wybieraILeft.F
metodę , mimo żeIBase.F
wydaje się, że nie jest ukryta w ścieżce dostępu prowadzącej przezIRight
element .Intuicyjna reguła ukrywania się w interfejsach wielokrotnego dziedziczenia jest po prostu następująca: jeśli element członkowski jest ukryty w dowolnej ścieżce dostępu, jest ukryty we wszystkich ścieżkach dostępu. Ponieważ ścieżka dostępu z
IDerived
doILeft
doIBase
ukrywaIBase.F
element członkowski jest również ukryty w ścieżce dostępu zIDerived
do .IRight
IBase
przykład końcowy
18.5 Kwalifikowane nazwy składowych interfejsu
Element członkowski interfejsu jest czasami określany przez jego kwalifikowaną nazwę elementu członkowskiego interfejsu. Kwalifikowana nazwa elementu członkowskiego interfejsu składa się z nazwy interfejsu, w którym zadeklarowano element członkowski, a następnie kropkę, a następnie nazwę elementu członkowskiego. Kwalifikowana nazwa elementu członkowskiego odwołuje się do interfejsu, w którym element członkowski jest zadeklarowany.
Przykład: biorąc pod uwagę deklaracje
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
kwalifikowana
Paint
nazwa toIControl.Paint
, a kwalifikowana nazwa elementu SetText toITextBox.SetText
. W powyższym przykładzie nie można odwoływać się doPaint
elementu .ITextBox.Paint
przykład końcowy
Gdy interfejs jest częścią przestrzeni nazw, kwalifikowana nazwa elementu członkowskiego interfejsu może zawierać nazwę przestrzeni nazw.
Przykład:
namespace System { public interface ICloneable { object Clone(); } }
System
Zarówno w przestrzeni nazw, jakICloneable.Clone
iSystem.ICloneable.Clone
są kwalifikowane nazwy składowe interfejsuClone
dla metody .przykład końcowy
Implementacje interfejsu 18.6
18.6.1 Ogólne
Interfejsy mogą być implementowane przez klasy i struktury. Aby wskazać, że klasa lub struktura bezpośrednio implementuje interfejs, interfejs znajduje się na liście klas bazowych klasy lub struktury.
Przykład:
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
przykład końcowy
Klasa lub struktura, która bezpośrednio implementuje interfejs, również niejawnie implementuje wszystkie interfejsy podstawowe interfejsu. Jest to prawdą, nawet jeśli klasa lub struktura nie wyświetla jawnie listy wszystkich interfejsów bazowych na liście klas bazowych.
Przykład:
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
W tym miejscu klasa
TextBox
implementuje zarówno klasy , jakIControl
iITextBox
.przykład końcowy
Gdy klasa C
bezpośrednio implementuje interfejs, wszystkie klasy pochodzące również z C
niejawnego implementowania interfejsu.
Interfejsy podstawowe określone w deklaracji klasy mogą być konstruowane typy interfejsów (§8.4, §18.2).
Przykład: Poniższy kod ilustruje, jak klasa może implementować skonstruowane typy interfejsów:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
przykład końcowy
Interfejsy podstawowe deklaracji klasy ogólnej spełniają regułę unikatowości opisaną w §18.6.3.
18.6.2 Implementacje jawnych elementów członkowskich interfejsu
Do celów implementowania interfejsów klasa lub struktura może zadeklarować jawne implementacje składowych interfejsu. Jawna implementacja elementu członkowskiego interfejsu jest metodą, właściwością, zdarzeniem lub deklaracją indeksatora, która odwołuje się do kwalifikowanej nazwy elementu członkowskiego interfejsu.
Przykład:
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) {...} }
Oto
IDictionary<int,T>.this
iIDictionary<int,T>.Add
są jawne implementacje składowych interfejsu.przykład końcowy
Przykład: W niektórych przypadkach nazwa elementu członkowskiego interfejsu może nie być odpowiednia dla klasy implementowania, w takim przypadku element członkowski interfejsu może być implementowany przy użyciu jawnej implementacji składowej interfejsu. Klasa implementujący abstrakcję pliku, na przykład, prawdopodobnie implementuje funkcję składową
Close
, która ma wpływ na zwalnianie zasobu pliku, i implementujeDispose
metodę interfejsuIDisposable
przy użyciu jawnej implementacji składowej interfejsu: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); } }
przykład końcowy
Nie można uzyskać dostępu do implementacji jawnego elementu członkowskiego interfejsu za pośrednictwem nazwy kwalifikowanego elementu członkowskiego interfejsu w wywołaniu metody, dostęp do właściwości, dostęp do zdarzeń lub dostęp indeksatora. Jawna implementacja elementu członkowskiego interfejsu może być dostępna tylko za pośrednictwem wystąpienia interfejsu i w takim przypadku jest przywoływane po prostu przez jego nazwę elementu członkowskiego.
Jest to błąd czasu kompilacji dla jawnej implementacji elementu członkowskiego interfejsu, aby uwzględnić wszelkie modyfikatory (§15.6) inne niż extern
lub async
.
Jest to błąd czasu kompilacji implementacji metody jawnego interfejsu, aby uwzględnić type_parameter_constraints_clauses. Ograniczenia implementacji ogólnej metody jawnego interfejsu są dziedziczone z metody interfejsu.
Uwaga: Implementacje jawnych elementów członkowskich interfejsu mają różne cechy ułatwień dostępu niż inne elementy członkowskie. Ponieważ jawne implementacje składowych interfejsu nigdy nie są dostępne za pośrednictwem kwalifikowanej nazwy elementu członkowskiego interfejsu w wywołaniu metody lub dostępu do właściwości, są one w sensie prywatne. Jednak ponieważ dostęp do nich można uzyskać za pośrednictwem interfejsu, są one również tak publiczne, jak interfejs, w którym są deklarowane. Jawne implementacje składowych interfejsu służą dwóm podstawowym celom:
- Ponieważ jawne implementacje składowych interfejsu nie są dostępne za pośrednictwem wystąpień klas lub struktur, umożliwiają one wykluczenie implementacji interfejsu z interfejsu publicznego klasy lub struktury. Jest to szczególnie przydatne, gdy klasa lub struktura implementuje interfejs wewnętrzny, który nie jest zainteresowany konsumentem tej klasy lub struktury.
- Jawne implementacje składowych interfejsu umożliwiają uściślanie elementów członkowskich interfejsu z tym samym podpisem. Bez jawnych implementacji składowych interfejsu byłoby niemożliwe, aby klasa lub struktura miała różne implementacje składowych interfejsu z tym samym typem podpisu i zwracania, co byłoby niemożliwe dla klasy lub struktury mieć jakąkolwiek implementację we wszystkich składowych interfejsu z tym samym podpisem, ale z różnymi typami zwracanymi.
notatka końcowa
Aby jawna implementacja składowa interfejsu jest prawidłowa, klasa lub struktura będzie nazywać interfejs na liście klas bazowych, który zawiera składową, której kwalifikowana nazwa składowa interfejsu, typ, liczba parametrów typu i typy parametrów dokładnie pasują do tych z jawnej implementacji składowej interfejsu. Jeśli element członkowski funkcji interfejsu ma tablicę parametrów, odpowiedni parametr skojarzonej implementacji jawnego elementu członkowskiego interfejsu jest dozwolony, ale nie jest wymagany, aby params
modyfikator. Jeśli element członkowski funkcji interfejsu nie ma tablicy parametrów, skojarzona jawna implementacja składowa interfejsu nie ma tablicy parametrów.
Przykład: W związku z tym w następującej klasie
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
deklaracja
IComparable.CompareTo
wyników w błędzie czasu kompilacji, ponieważIComparable
nie znajduje się na liście klas bazowychShape
i nie jest podstawowym interfejsemICloneable
. Podobnie, w deklaracjachclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
deklaracja
ICloneable.Clone
wEllipse
wynikach błędu czasu kompilacji, ponieważICloneable
nie jest jawnie wymieniona na liście klas bazowych .Ellipse
przykład końcowy
Kwalifikowana nazwa składowa interfejsu implementacji jawnego elementu członkowskiego interfejsu odwołuje się do interfejsu, w którym element członkowski został zadeklarowany.
Przykład: W ten sposób w deklaracjach
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
jawna implementacja elementu członkowskiego interfejsu programu Paint musi być napisana jako
IControl.Paint
, a nieITextBox.Paint
.przykład końcowy
18.6.3 Unikatowość zaimplementowanych interfejsów
Interfejsy implementowane przez deklarację typu ogólnego pozostają unikatowe dla wszystkich możliwych typów skonstruowanych. Bez tej reguły nie można określić prawidłowej metody wywoływania niektórych skonstruowanych typów.
Przykład: Załóżmy, że można napisać deklarację klasy ogólnej w następujący sposób:
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() {...} }
Gdyby to było dozwolone, nie można określić, który kod ma zostać wykonany w następującym przypadku:
I<int> x = new X<int, int>(); x.F();
przykład końcowy
Aby ustalić, czy lista interfejsów deklaracji typu ogólnego jest prawidłowa, wykonywane są następujące kroki:
- Niech
L
lista interfejsów jest bezpośrednio określona w klasie ogólnej, struktury lub deklaracji interfejsuC
. - Dodaj do
L
wszystkich podstawowych interfejsów interfejsów już w plikuL
. - Usuń wszelkie duplikaty z pliku
L
. - Jeśli jakikolwiek możliwy typ skonstruowany utworzony na podstawie
C
metody, po zastąpieniu argumentów typu doL
elementu , powodują, że dwa interfejsyL
są identyczne, oznacza to, że deklaracjaC
jest nieprawidłowa. Deklaracje ograniczeń nie są brane pod uwagę podczas określania wszystkich możliwych typów skonstruowanych.
Uwaga: W powyższej deklaracji
X
klasy listaL
interfejsów składa się zl<U>
elementów iI<V>
. Deklaracja jest nieprawidłowa, ponieważ każdy skonstruowany typ iU
V
będący tym samym typem spowoduje, że te dwa interfejsy będą identyczne. notatka końcowa
Istnieje możliwość ujednolicenia interfejsów określonych na różnych poziomach dziedziczenia:
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() {...}
}
Ten kod jest prawidłowy, mimo że Derived<U,V>
implementuje zarówno elementy , jak I<U>
i I<V>
. Kod
I<int> x = new Derived<int, int>();
x.F();
wywołuje metodę w metodzie , Derived
ponieważ Derived<int,int>'
skutecznie ponownie implementuje I<int>
(§18.6.7).
18.6.4 Implementacja metod ogólnych
Gdy metoda ogólna niejawnie implementuje metodę interfejsu, ograniczenia podane dla każdego parametru typu metody są równoważne w obu deklaracjach (po zastąpieniu jakichkolwiek parametrów typu interfejsu odpowiednimi argumentami typu), gdzie parametry typu metody są identyfikowane przez pozycje porządkowe, od lewej do prawej.
Przykład: W poniższym kodzie:
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>
niejawnie implementuje metodęI<object,C,string>.F<T>
. W takim przypadku nie jest wymagane (ani dozwolone) określenie ograniczeniaT: object
,C.F<T>
ponieważobject
jest niejawnym ograniczeniem dla wszystkich parametrów typu. MetodaC.G<T>
niejawnie implementujeI<object,C,string>.G<T>
, ponieważ ograniczenia są zgodne z ograniczeniami w interfejsie, po zastąpieniu parametrów typu interfejsu odpowiednimi argumentami typu. Ograniczenie dla metodyC.H<T>
jest błędem, ponieważ zapieczętowane typy (string
w tym przypadku) nie mogą być używane jako ograniczenia. Pominięcie ograniczenia byłoby również błędem, ponieważ ograniczenia implementacji niejawnych metod interfejsu są wymagane do dopasowania. W związku z tym nie można niejawnie zaimplementowaćI<object,C,string>.H<T>
elementu . Tę metodę interfejsu można zaimplementować tylko przy użyciu jawnej implementacji składowej interfejsu: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); } }
W tym przypadku implementacja jawnego elementu członkowskiego interfejsu wywołuje metodę publiczną o ściśle słabszych ograniczeniach. Przypisanie z t do s jest prawidłowe, ponieważ
T
dziedziczy ograniczenieT: string
, mimo że to ograniczenie nie jest wyrażalne w kodzie źródłowym. przykład końcowy
Uwaga: Jeśli metoda ogólna jawnie implementuje metodę interfejsu, nie są dozwolone żadne ograniczenia w metodzie implementowania (§15.7.1, §18.6.2). notatka końcowa
Mapowanie interfejsu 18.6.5
Klasa lub struktura zapewnia implementacje wszystkich elementów członkowskich interfejsów wymienionych na liście klas bazowych klasy lub struktury. Proces lokalizowania implementacji elementów członkowskich interfejsu w implementacji klasy lub struktury jest znany jako mapowanie interfejsu.
Mapowanie interfejsu dla klasy lub struktury C
lokalizuje implementację dla każdego elementu członkowskiego każdego interfejsu określonego na liście klas bazowych C
. Implementacja określonego elementu członkowskiego I.M
interfejsu , gdzie I
jest interfejsem, w którym zadeklarowany jest element członkowski M
, jest określana przez zbadanie każdej klasy lub struktury S
, począwszy od C
i powtarzanie dla każdej kolejnej klasy bazowej C
, aż do dopasowania znajduje się:
- Jeśli
S
zawiera deklarację jawnej implementacji elementu członkowskiego interfejsu, która jest zgodnaI
z elementem ,M
element członkowski jest implementacją .I.M
- W przeciwnym razie, jeśli
S
zawiera deklarację niestacjonanego publicznego elementu członkowskiego zgodnegoM
z elementem , ten element członkowski jest implementacją elementuI.M
. Jeśli więcej niż jeden element członkowski jest zgodny, nie jest określony, który element członkowski jest implementacją .I.M
Taka sytuacja może wystąpić tylko wtedy, gdyS
jest skonstruowanym typem, w którym dwa elementy członkowskie zadeklarowane w typie ogólnym mają różne podpisy, ale argumenty typu tworzą identyczne podpisy.
Błąd czasu kompilacji występuje, jeśli implementacje nie mogą być zlokalizowane dla wszystkich elementów członkowskich wszystkich interfejsów określonych na liście klas bazowych .C
Elementy członkowskie interfejsu obejmują te elementy członkowskie, które są dziedziczone z interfejsów podstawowych.
Elementy członkowskie skonstruowanego typu interfejsu są uważane za parametry typu zastąpione odpowiednimi argumentami typu określonymi w §15.3.3.
Przykład: na przykład, biorąc pod uwagę deklarację interfejsu ogólnego:
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }
skonstruowany interfejs
I<string[]>
ma elementy członkowskie:string[] F(int x, string[,][] y); string[] this[int y] { get; }
przykład końcowy
Do celów mapowania interfejsu składowa A
klasy lub struktury jest zgodna z składową B
interfejsu, gdy:
A
iB
są metodami, a nazwa, typ i listy parametrówA
iB
są identyczne.A
iB
są właściwościami, nazwą i typemA
iB
są identyczne iA
mają takie same metody dostępu jakB
(A
może mieć dodatkowe metody dostępu, jeśli nie jest to jawna implementacja elementu członkowskiego interfejsu).A
iB
są zdarzeniami, a nazwa i typA
iB
są identyczne.A
iB
są indeksatorami, listami typów i parametrówA
iB
są identyczne iA
mają takie same metody dostępu jakB
(A
może mieć dodatkowe metody dostępu, jeśli nie jest to jawna implementacja elementu członkowskiego interfejsu).
Istotne implikacje algorytmu mapowania interfejsu to:
- Implementacje jawnych składowych interfejsu mają pierwszeństwo przed innymi elementami członkowskimi w tej samej klasie lub struktury podczas określania składowej klasy lub struktury, która implementuje składową interfejsu.
- Ani niepublijni, ani statyczni członkowie nie uczestniczą w mapowaniu interfejsu.
Przykład: w poniższym kodzie
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
element
ICloneable.Clone
członkowskiC
staje się implementacjąClone
elementu "ICloneable", ponieważ jawne implementacje elementów członkowskich interfejsu mają pierwszeństwo przed innymi elementami członkowskimi.przykład końcowy
Jeśli klasa lub struktura implementuje co najmniej dwa interfejsy zawierające składową o tej samej nazwie, typie i typie parametrów, można mapować każdy z tych elementów członkowskich interfejsu na jedną klasę lub składową struktury.
Przykład:
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
Paint
W tym miejscu metody iIForm
IControl
są mapowane na metodęPaint
w plikuPage
. Oczywiście istnieje również możliwość posiadania oddzielnych jawnych implementacji składowych interfejsu dla tych dwóch metod.przykład końcowy
Jeśli klasa lub struktura implementuje interfejs zawierający ukryte elementy członkowskie, niektóre elementy członkowskie mogą być implementowane za pomocą jawnych implementacji składowych interfejsu.
Przykład:
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
Implementacja tego interfejsu wymagałaby co najmniej jednej jawnej implementacji składowej interfejsu i miałaby jedną z następujących formularzy
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() {...} }
przykład końcowy
Gdy klasa implementuje wiele interfejsów, które mają ten sam interfejs podstawowy, może istnieć tylko jedna implementacja interfejsu podstawowego.
Przykład: w poniższym kodzie
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) {...} }
Nie można mieć oddzielnych implementacji dla
IControl
nazwy na liście klas bazowych,IControl
dziedziczone przezITextBox
, iIControl
dziedziczone przezIListBox
. W rzeczywistości nie ma pojęcia oddzielnej tożsamości dla tych interfejsów. Zamiast tego implementacjeITextBox
iIListBox
współużytkują tę samą implementacjęIControl
, iComboBox
są po prostu uważane za implementację trzech interfejsów,IControl
,ITextBox
iIListBox
.przykład końcowy
Elementy członkowskie klasy bazowej uczestniczą w mapowaniu interfejsu.
Przykład: w poniższym kodzie
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
metoda
F
w metodzie jestClass1
używana weClass2's
wdrożeniu metodyInterface1
.przykład końcowy
Dziedziczenie implementacji interfejsu 18.6.6
Klasa dziedziczy wszystkie implementacje interfejsu udostępniane przez jej klasy bazowe.
Bez jawnego ponownego implementowania interfejsu klasa pochodna nie może w żaden sposób zmienić mapowań interfejsu dziedziczącego z klas bazowych.
Przykład: w deklaracjach
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
Paint
metoda wTextBox
obiekcie ukrywa metodęPaint
wControl
metodzie , ale nie zmienia mapowaniaControl.Paint
metody naIControl.Paint
, a wywołaniaPaint
metody za pośrednictwem wystąpień klasy i wystąpień interfejsu będą miały następujące efektyControl 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();
przykład końcowy
Jednak gdy metoda interfejsu jest mapowana na metodę wirtualną w klasie, istnieje możliwość zastąpienia metody wirtualnej przez klasy pochodne i zmiany implementacji interfejsu.
Przykład: ponowne zapisywanie powyższych deklaracji na
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }
następujące efekty będą teraz obserwowane
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();
przykład końcowy
Ponieważ jawne implementacje składowych interfejsu nie mogą być zadeklarowane jako wirtualne, nie można zastąpić jawnej implementacji składowej interfejsu. Jednak implementacja jawnego elementu członkowskiego interfejsu jest prawidłowa, aby wywołać inną metodę i że można zadeklarować inną metodę wirtualną, aby umożliwić jej zastąpienie klas pochodnych.
Przykład:
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }
W tym miejscu klasy pochodzące z
Control
klasy mogą specjalizować implementacjęIControl.Paint
przez zastąpieniePaintControl
metody.przykład końcowy
Ponowne wdrożenie interfejsu 18.6.7
Klasa, która dziedziczy implementację interfejsu, może ponownie zaimplementować interfejs, dołączając go do listy klas bazowych.
Ponowna implementacja interfejsu jest zgodna z dokładnie tymi samymi regułami mapowania interfejsu co początkowa implementacja interfejsu. W związku z tym mapowanie odziedziczonego interfejsu nie ma żadnego wpływu na mapowanie interfejsu ustanowione na potrzeby ponownej implementacji interfejsu.
Przykład: w deklaracjach
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }
fakt, że
Control
mapujeIControl.Paint
naControl.IControl.Paint
nie ma wpływu na ponowną implementację w obiekcieMyControl
, który mapujeIControl.Paint
naMyControl.Paint
.przykład końcowy
Dziedziczone publiczne deklaracje składowe i dziedziczone jawne deklaracje składowe interfejsu uczestniczą w procesie mapowania interfejsu dla ponownie zaimplementowanych interfejsów.
Przykład:
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() {} }
Tutaj implementacja metody w programie mapuje metody interfejsu
IMethods
naDerived.F
metody ,Base.IMethods.G
,Derived.IMethods.H
iBase.I
.Derived
przykład końcowy
Gdy klasa implementuje interfejs, niejawnie implementuje również wszystkie interfejsy podstawowe tego interfejsu. Podobnie ponowna implementacja interfejsu jest również niejawnie ponownie implementacją wszystkich interfejsów podstawowych interfejsu.
Przykład:
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() {...} }
W tym miejscu ponownie zaimplementowana
IBase
jest również implementacjaIDerived
metody , mapowaniaIBase.F
na .D.F
przykład końcowy
18.6.8 Klasy abstrakcyjne i interfejsy
Podobnie jak klasa nie abstrakcyjna, klasa abstrakcyjna zapewnia implementacje wszystkich elementów członkowskich interfejsów wymienionych na liście klas bazowych klasy. Jednak klasa abstrakcyjna może mapować metody interfejsu na metody abstrakcyjne.
Przykład:
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
W tym miejscu implementacja
IMethods
mapF
iG
metod abstrakcyjnych, które są zastępowane w klasach nie abstrakcyjnych, które pochodzą zC
klasy .przykład końcowy
Jawne implementacje składowych interfejsu nie mogą być abstrakcyjne, ale jawne implementacje składowych interfejsu są oczywiście dozwolone do wywoływania metod abstrakcyjnych.
Przykład:
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(); }
W tym miejscu klasy nie abstrakcyjne, które pochodzą z
C
klasy, byłyby wymagane do zastąpieniaFF
iGG
, zapewniając rzeczywistą implementacjęIMethods
klasy .przykład końcowy
ECMA C# draft specification