Przeczytaj w języku angielskim

Udostępnij za pośrednictwem


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 członów, 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

Deklaracja interfejsu (interface_declaration) to deklaracja typu (type_declaration) (§14.7), która deklaruje nowy typ interfejsu.

ANTLR
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_modifierów (§18.2.2), a następnie opcjonalny modyfikator częściowy (§15.2.7), po którym następuje słowo kluczowe interface, następnie identyfikator, który nazywa interfejs, a następnie opcjonalna specyfikacja variant_type_parameter_list (§18.2.3), a następnie opcjonalna specyfikacja interface_base (§18.2.4), po której następuje opcjonalna specyfikacja type_parameter_constraints_clause (§15.2.5), a następnie interface_body (§18.3), opcjonalnie zakończony średnikiem.

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:

ANTLR
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 człon o tej samej nazwie, zgodnie z opisem w §15.3.5.

Modyfikatory public, protectedinternali 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ę dostępności za pośrednictwem modyfikatorów public, protected, internal i private, obowiązują reguły z §15.2.2.

18.2.3 Listy parametrów typu wariantu

18.2.3.1 Ogólne

Listy parametrów wariantów mogą występować tylko w typach interfejsów i delegatów. Różnica w stosunku do standardowych type_parameter_list polega na opcjonalnym variance_annotation dla każdego z parametrów typu.

ANTLR
variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : attributes? variance_annotation? type_parameter
    ;

variance_annotation
    : 'in'
    | 'out'
    ;

Jeśli adnotacja wariancji to out, parametr typu uznaje się za kowariantny. Jeśli adnotacja wariancji to in, parametr typu mówi się, że jest kontrawariantny. Jeśli nie ma adnotacji wariancji, mówi się, że parametr typu jest niezmienny.

Przykład: w następujących kwestiach:

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

X jest kowariantny, Y jest kontrawariantny i Z 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 wyników, jeśli zachodzi jeden z następujących warunków:

  • T jest kontrawariantnym parametrem typu
  • T jest typem tablicowym, którego elementy są typu niebezpiecznego dla wyniku
  • T to interfejs lub typ Sᵢ,... Aₑ delegata skonstruowany z typu S<Xᵢ, ... Xₑ> ogólnego, w którym co najmniej jeden Aᵢ z następujących dotyczy:
    • Xᵢ jest kowariantny lub niezmienny i Aᵢ jest niebezpieczny dla danych wyjściowych.
    • Xᵢ jest kontrawariantny lub niezmienny i Aᵢ jest niebezpieczny dla danych wejściowych.

Typ T jest niebezpieczny dla wejścia, jeśli spełniony jest jeden z następujących warunków:

  • T jest kowariantnym parametrem typu
  • T jest typem tablicy, którego elementy są niebezpieczne dla danych wejściowych
  • T to interfejs lub typ delegata S<Aᵢ,... Aₑ> skonstruowany z typu ogólnego S<Xᵢ, ... Xₑ>, dla którego co najmniej jeden Aᵢ spełnia jeden z następujących warunków:
    • Xᵢ jest kowariantny lub niezmienny i Aᵢ jest niebezpieczny dla danych wejściowych.
    • Xᵢ jest kontrawariantny lub niezmienny i Aᵢ jest niebezpieczny dla danych wyjściowych.

Intuicyjnie rzecz biorąc, typ niebezpieczny dla wyjścia jest zabroniony w pozycji wyjściowej, a typ niebezpieczny dla wejścia 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 umożliwienie bardziej elastycznych (ale nadal zapewniających bezpieczeństwo typów) konwersji na typy interfejsów i delegatów. W tym celu definicje niejawnej (§10.2) i jawnej konwersji (§10.3) korzystają z pojęcia konwertowalności wariancji, która jest zdefiniowana w następujący sposób:

Typ T<Aᵢ, ..., Aᵥ> jest zmienny do typu T<Bᵢ, ..., Bᵥ>, jeśli T jest interfejsem lub typem delegata zadeklarowanym z parametrami typu wariantowego T<Xᵢ, ..., Xᵥ>, a dla każdego parametru typu wariantowego Xᵢ dotyczy jedno z poniższych.

  • Xᵢ jest kowariantny, a niejawne odwołanie lub konwersja tożsamości istnieje z Aᵢ do Bᵢ
  • Xᵢ jest kontrawariantny, a niejawne odwołanie referencyjne lub konwersja tożsamości istnieje z Bᵢ do Aᵢ
  • Xᵢ jest niezmienny, a konwersja identyczności istnieje z Aᵢ do Bᵢ

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 po nim dwukropek oraz lista typów interfejsów podstawowych rozdzielanych przecinkami.

ANTLR
interface_base
    : ':' interface_type_list
    ;

Jawne interfejsy podstawowe mogą być konstruowane jako 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.

Dla skonstruowanego typu interfejsu jawne interfejsy bazowe są tworzone poprzez przyjęcie jawnych deklaracji tych interfejsów w definicji typu ogólnego, a następnie zastąpienie każdego type_parameter w deklaracji interfejsu bazowego odpowiednim type_argument skonstruowanego typu.

Jawne interfejsy podstawowe interfejsu są co najmniej tak dostępne, jak sam interfejs (§7.5.5).

Uwaga: na przykład jest to błąd czasu kompilacji polegający na określeniu interfejsu private lub internal w interface_base interfejsu public. notatka końcowa

Jest to błąd czasu kompilacji, gdy interfejs bezpośrednio lub pośrednio dziedziczy z samego siebie.

Podstawowe interfejsy interfejsu to jawne interfejsy podstawowe i ich podstawowe interfejsy. Innymi słowy, zestaw interfejsów bazowych jest kompletnym przechodnim zamknięciem jawnych interfejsów bazowych, ich jawnych interfejsów bazowych itd. Interfejs dziedziczy wszystkie elementy członkowskie interfejsów podstawowych.

Przykład: w poniższym kodzie

C#
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 IComboBoxIControl, ITextBoxi IListBox. Innemi słowy, interfejs IComboBox powyżej dziedziczy członków SetText, SetItems a także Paint.

przykład końcowy

Składowe dziedziczone z skonstruowanego typu ogólnego są dziedziczone po podstawieniu typu. Oznacza to, że wszystkie typy składowe mają parametry typu deklaracji klasy bazowej zastąpione odpowiednimi argumentami typu używanymi w specyfikacji class_base.

Przykład: w poniższym kodzie

C#
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 tym jak parametr typu T jest zastąpiony string[,].

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

ANTLR
interface_body
    : '{' interface_member_declaration* '}'
    ;

18.4 Elementy członkowskie interfejsu

18.4.1 Ogólne

Elementy członkowskie interfejsu to elementy dziedziczone z interfejsów bazowych oraz elementy zadeklarowane przez sam interfejs.

ANTLR
interface_member_declaration
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

Deklaracja interfejsu deklaruje zero lub więcej składników. 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 błędem czasu kompilacji, jeśli deklaracje elementów interfejsu zawierają jakiekolwiek modyfikatory.

Deklaracja interfejsu tworzy nową przestrzeń deklaracji (§7.3), a parametry typu i człony interfejsu natychmiast objęte przez deklarację interfejsu wprowadzają nowe elementy 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, outi ref.
  • Nazwa właściwości lub zdarzenia powinna 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 wyraźnie nie są częścią przestrzeni deklaracji interfejsu. W związku z tym interfejs może zadeklarować członka o tej samej nazwie lub podpisie co dziedziczony członek. W takim przypadku mówi się, że element członkowski interfejsu pochodnego ukrywa podstawowy element członkowski interfejsu. Ukrycie dziedziczonego elementu nie jest traktowane jako błąd, co powoduje, że kompilator wydaje 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.

Jeśli modyfikator new został uwzględniony w deklaracji, która nie ukrywa dziedziczonego elementu członkowskiego, w związku z tym zostanie wydane ostrzeżenie. To ostrzeżenie jest pomijane przez usunięcie new modyfikatora.

Uwaga: Członkowie klasy object nie są, ściśle mówiąc, członkami żadnego interfejsu (§18.4). Jednak składowe w klasie object 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:

ANTLR
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ć void lub bezpieczny dla danych wyjściowych. 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:

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

jest źle sformułowany, ponieważ użycie T jako ograniczenia parametru typu U nie jest bezpieczne przy wprowadzaniu danych.

Gdyby to ograniczenie nie zostało wprowadzone, byłoby możliwe naruszenie bezpieczeństwa typu w następujący sposób:

C#
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, aby E pochodziło z D, a więc bezpieczeństwo typu byłoby tutaj naruszone.

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:

ANTLR
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).

Akcesory deklaracji właściwości interfejsu odpowiadają akcesorom 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 do odczytu i zapisu, tylko do odczytu, czy tylko do zapisu.

Typ właściwości interfejsu musi być bezpieczny dla odczytu, jeśli istnieje akcesor do odczytu, i musi być bezpieczny dla zapisu, jeśli istnieje akcesor do zapisu.

Zdarzenia interfejsu 18.4.4

Zdarzenia interfejsu są deklarowane przy użyciu interface_event_declarations:

ANTLR
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:

ANTLR
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).

Akcesory deklaracji indeksatora interfejsu odpowiadają akcesorom deklaracji indeksatora klasy (§15.9), z wyjątkiem tego, że accessor_body zawsze powinno być średnikiem. W związku z tym metody dostępu po prostu wskazują, czy indeksator jest 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 jest bezpieczny przy odczycie, jeśli posiada metodę get, i bezpieczny przy zapisie, jeśli posiada metodę set.

18.4.6 Dostęp do składowych interfejsu

Dostęp do członków interfejsu jest uzyskiwany za pomocą dostępu do składowych (§12.8.7) i dostępu indeksatora (§12.8.12.3) wyrażenia w formie I.M i I[A], gdzie I jest typem interfejsu, M jest metodą, właściwością lub zdarzeniem tego typu interfejsu, a A jest listą argumentów indeksatora.

W przypadku interfejsów, które są ściśle pojedynczego dziedziczenia (każdy interfejs w łańcuchu dziedziczenia ma dokładnie zero lub jeden bezpośredni interfejs podstawowy), wyniki wyszukiwania składowych (§12.5), wywoływania metody (§12.8.10.2) i dostępu do indeksatora (§12.8.12.3) są dokładnie takie same jak w przypadku klas i struktur: Bardziej pochodne składowe ukrywają mniej pochodne składowe o tej samej nazwie lub sygnaturze. Jednak w przypadku interfejsów wielokrotnego dziedziczenia mogą wystąpić niejednoznaczności, gdy dwa lub więcej niepowiązanych interfejsów podstawowych deklaruje członków o tej samej nazwie lub podpisie. W tym podpunkcie przedstawiono kilka przykładów, z których niektóre prowadzą do niejednoznaczności, a inne nie. We wszystkich przypadkach jawne rzutowania mogą służyć do rozwiązywania niejednoznaczności.

Przykład: w poniższym kodzie

C#
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 kompilacji, ponieważ wyszukiwanie elementu członkowskiego (§12.5) Count w IListCounter jest niejednoznaczne. Jak ilustruje przykład, niejednoznaczność jest rozwiązana przez rzutowanie x do odpowiedniego podstawowego typu interfejsu. Takie rzutowania nie mają kosztów czasu wykonywania — polegają jedynie na traktowaniu instancji jako mniej pochodnego typu podczas kompilacji.

przykład końcowy

Przykład: w poniższym kodzie

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

Wywołanie n.Add(1) wybiera IInteger.Add poprzez zastosowanie reguł rozwiązywania przeciążenia §12.6.4. Podobnie wywołanie n.Add(1.0) wybiera IDouble.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

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

członek IBase.F jest ukryty przez członka ILeft.F. Wywołanie d.F(1) wybiera ILeft.F, mimo że IBase.F wydaje się nie być ukryta w ścieżce dostępu prowadzącej przez IRight.

Intuicyjna reguła ukrywania w interfejsach wielokrotnego dziedziczenia jest po prostu taka: jeśli członek jest ukryty w jakiejkolwiek ścieżce dostępu, ukryty jest we wszystkich ścieżkach dostępu. Ponieważ ścieżka dostępu z IDerived do ILeft do IBase ukrywa IBase.F, element członkowski jest również ukryty w ścieżce dostępu z IDerived do IRight do IBase.

przykład końcowy

18.5 Kwalifikowane nazwy członków interfejsu

Element członkowski interfejsu jest czasami określany przez jego kwalifikowaną nazwę członkowską 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

C#
interface IControl
{
    void Paint();
}

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

Nazwa kwalifikowana Paint to IControl.Paint, a nazwa kwalifikowana SetText to ITextBox.SetText. W powyższym przykładzie nie można się odwoływać do Paint jako do 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:

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

W przestrzeni nazw System zarówno ICloneable.Clone, jak i System.ICloneable.Clone są zakwalifikowane jako nazwy składowe interfejsu dla metody Clone.

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:

C#
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:

C#
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 , jak IControl i ITextBox.

przykład końcowy

Gdy klasa C bezpośrednio implementuje interfejs, wszystkie klasy dziedziczące z C również implementują ten interfejs niejawnie.

Interfejsy podstawowe określone w deklaracji klasy mogą być typami konstrukcji interfejsów (§8.4, §18.2).

Przykład: Poniższy kod ilustruje, jak klasa może implementować skonstruowane typy interfejsów:

C#
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:

C#
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 i IDictionary<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ąca abstrakcję pliku prawdopodobnie zawiera funkcję członkowską Close, która zwalnia zasób pliku, i implementuje metodę Dispose interfejsu IDisposable przy użyciu jawnej implementacji członków interfejsu.

C#
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 członka interfejsu za pośrednictwem nazwy kwalifikowanego członka interfejsu w wywołaniu metody, dostępie do właściwości, zdarzeń lub indeksatora. Jawna implementacja członka interfejsu może być dostępna tylko za pośrednictwem wystąpienia interfejsu i w takim przypadku jest przywoływana po prostu po jego nazwie.

Błąd kompilacji występuje w przypadku jawnej implementacji członka interfejsu, jeśli stosowane są jakiekolwiek modyfikatory (§15.6) inne niż extern lub async.

Jest to błąd czasu kompilacji, jeśli implementacja metody interfejsu jawnego zawiera type_parameter_constraints_clauses. Ograniczenia ogólnej implementacji 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 elementów interfejsu nigdy nie są dostępne poprzez kwalifikowaną nazwę członka interfejsu w wywołaniu metody lub przy dostępie do właściwości, są w pewnym 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 wzbudza zainteresowania konsumenta tej klasy lub struktury.
  • Jawne implementacje składowych interfejsu umożliwiają rozróżnienie składowych interfejsu o tym samym podpisie. Bez jawnych implementacji składowych interfejsu klasa lub struktura nie mogłaby mieć różnych implementacji składowych interfejsu z tą samą sygnaturą i typem zwracanym, podobnie jak nie mogłaby mieć żadnej implementacji składowych interfejsu z tą samą sygnaturą, ale różnymi typami zwracanymi.

notatka końcowa

Aby jawna implementacja członka interfejsu była prawidłowa, klasa lub struktura musi wymieniać interfejs na swojej liście klas bazowych, który zawiera członka, którego kwalifikowana nazwa członka interfejsu, typ, liczba parametrów typu i typy parametrów dokładnie pasują do tych z jawnej implementacji członka interfejsu. Jeśli element członkowski funkcji interfejsu ma tablicę parametrów, odpowiedni parametr w skojarzonej implementacji jawnego elementu członkowskiego interfejsu może mieć modyfikator params, ale nie jest to wymagane. Jeśli element członkowski funkcji interfejsu nie ma tablicy parametrów, to skojarzona jawna implementacja elementu członkowskiego interfejsu również nie powinna mieć tablicy parametrów.

Przykład: Zatem w następującej klasie

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

Deklaracja IComparable.CompareTo powoduje błąd czasu kompilacji, ponieważ IComparable nie jest wymieniony na liście klas bazowych Shape i nie jest interfejsem bazowym ICloneable. Podobnie, w deklaracjach

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

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

deklaracja ICloneable.Clone w Ellipse skutkuje błędem czasu kompilacji, ponieważ ICloneable nie jest jawnie wymieniona na liście klas bazowych Ellipse.

przykład końcowy

Kwalifikowana nazwa elementu członkowskiego jawnego interfejsu powinna odnosić się do interfejsu, w którym element członkowski został zadeklarowany.

Przykład: A zatem w deklaracjach

C#
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 nie ITextBox.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:

C#
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:

C#
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 będzie listą interfejsów bezpośrednio określonych w deklaracji klasy ogólnej, struktury lub interfejsu C.
  • Dodaj do L wszystkie podstawowe interfejsy, które są już w L.
  • Usuń wszelkie duplikaty z pliku L.
  • Jeśli jakikolwiek możliwy typ skonstruowany z C, po podstawieniu argumentów typu do L, spowoduje, że dwa interfejsy w L będą identyczne, to deklaracja C jest nieprawidłowa. Deklaracje ograniczeń nie są brane pod uwagę podczas określania wszystkich możliwych typów skonstruowanych.

Uwaga: W powyższej deklaracji klasy X, lista interfejsów L składa się z l<U> i I<V>. Deklaracja jest nieprawidłowa, ponieważ każdy skonstruowany typ, w którym U oraz V są tym samym typem, spowoduje, że te dwa interfejsy będą identycznymi typami. notatka końcowa

Istnieje możliwość ujednolicenia interfejsów określonych na różnych poziomach dziedziczenia:

C#
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

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

wywołuje metodę w 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:

C#
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 ograniczenia T: object, ponieważ object jest niejawnym ograniczeniem dla wszystkich parametrów typu. Metoda C.G<T> niejawnie implementuje I<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 metody C.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 zaimplementować I<object,C,string>.H<T> domyślnie. Tę metodę interfejsu można zaimplementować tylko przy użyciu jawnej implementacji składowej interfejsu:

C#
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 ograniczenie T: 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 członka interfejsu I.M, gdzie I jest interfejsem, w którym zadeklarowany jest członek M, jest określana przez zbadanie każdej klasy lub struktury S, począwszy od C i powtarzając dla każdej kolejnej klasy bazowej C, aż do znalezienia dopasowania:

  • Jeśli S zawiera deklarację jawnej implementacji członka interfejsu, która jest zgodna z I i M, to ten element członkowski jest implementacją I.M.
  • W przeciwnym razie, jeśli S zawiera deklarację niestatycznego publicznego elementu członkowskiego, który pasuje do M, to ten element członkowski jest implementacją I.M. Jeżeli więcej niż jeden element członkowski pasuje, nie jest określone, który element członkowski jest implementacją I.M. Taka sytuacja może wystąpić tylko wtedy, gdy S 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 nie można znaleźć implementacji dla wszystkich członków 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.

Członkowie skonstruowanego typu interfejsu są traktowani jako mający parametry typu zastąpione odpowiednimi argumentami typu, jak określono w §15.3.3.

Przykład: na przykład, biorąc pod uwagę deklarację interfejsu ogólnego:

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

interfejs I<string[]> ma członków:

C#
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 i B są metodami, a nazwa, typ i listy parametrów A i B są identyczne.
  • A i B są właściwościami, nazwy i typy A i B są identyczne, a A ma takie same metody dostępu jak B (A może mieć dodatkowe metody dostępu, jeśli nie jest jawnie implementowany jako element członkowski interfejsu).
  • A i B są zdarzeniami, a nazwa i typ A i B są identyczne.
  • A i B są indeksatorami, listy typów i parametrów A oraz B są identyczne, a A posiada te same akcesory co B (A może mieć dodatkowe akcesory, 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

C#
interface ICloneable
{
    object Clone();
}

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

ICloneable.Clone element członkowski C staje się implementacją Clone w ICloneable, ponieważ jawne implementacje składowych 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:

C#
interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

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

W tym miejscu metody zarówno IControl jak i IForm są mapowane na metodę Paint w Page. 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 składowe, niektóre składowe mogą być implementowane za pomocą jawnych implementacji członków interfejsu.

Przykład:

C#
interface IBase
{
    int P { get; }
}

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

Implementacja tego interfejsu wymagałaby co najmniej jednej jawnej implementacji składnika interfejsu i miałaby jedną z następujących form.

C#
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

C#
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 jest możliwe posiadanie odrębnych implementacji dla IControl, nazwanej na liście klas bazowych, IControl, której dziedziczenie odbywa się przez ITextBox, oraz IControl, której dziedziczenie odbywa się przez IListBox. W rzeczywistości nie ma pojęcia oddzielnej tożsamości dla tych interfejsów. Zamiast tego implementacje ITextBoxi IListBox współużytkują tę samą implementację IControl, i ComboBox są po prostu uważane za implementację trzech interfejsów, IControl, ITextBoxi IListBox.

przykład końcowy

Elementy członkowskie klasy bazowej uczestniczą w mapowaniu interfejsu.

Przykład: w poniższym kodzie

C#
interface Interface1
{
    void F();
}

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

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

metoda F w Class1 jest używana we wdrożeniu Class2'sInterface1.

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 zmodyfikować mapowań interfejsu, które dziedziczy z klas bazowych.

Przykład: w deklaracjach

C#
interface IControl
{
    void Paint();
}

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

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

Metoda Paint w TextBox ukrywa metodę Paint w Control, ale nie zmienia mapowania Control.Paint na IControl.Paint. Wywołania Paint za pośrednictwem instancji klasy i interfejsu będą miały następujące efekty.

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

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

C#
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

C#
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 może wywołać inną metodę, która może być zadeklarowana jako wirtualna, co umożliwia jej nadpisanie przez klasy pochodne.

Przykład:

C#
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 mogą specjalizować implementację IControl.Paint przez zastąpienie metody PaintControl.

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

C#
interface IControl
{
    void Paint();
}

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

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

fakt, że Control mapuje IControl.Paint na Control.IControl.Paint nie ma wpływu na ponowną implementację w obiekcie MyControl, który mapuje IControl.Paint na MyControl.Paint.

przykład końcowy

Dziedziczone publiczne deklaracje członków oraz dziedziczone jawne deklaracje członków interfejsu biorą udział w procesie mapowania interfejsu dla ponownie zaimplementowanych interfejsów.

Przykład:

C#
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 wdrożenie IMethods w Derived mapuje metody interfejsu na Derived.F, Base.IMethods.G, Derived.IMethods.H i Base.I.

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:

C#
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 ponowne zaimplementowanie IDerived również zaimplementowuje IBase, mapując IBase.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:

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

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

W tym miejscu implementacja IMethods mapuje F i G na metody abstrakcyjne, które są zastępowane w klasach nieabstrakcyjnych, które pochodzą z klasy C.

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:

C#
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ąpienia FF i GG, zapewniając rzeczywistą implementację IMethodsklasy .

przykład końcowy