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 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, 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ę ułatwień dostępu (za pośrednictwem publicmodyfikatorów , protected, internali 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 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 danych wyjściowych, jeśli jedna z następujących blokad:

  • T jest kontrawariantnym parametrem typu
  • T jest typem tablicy z typem elementu output-niebezpiecznego
  • 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 blokad:
    • 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 danych wejściowych, jeśli jedna z następujących blokad:

  • T jest kowariantnym parametrem typu
  • T jest typem tablicy z typem elementu input-niebezpiecznego
  • T to interfejs lub typ S<Aᵢ,... Aₑ> delegata skonstruowany z typu S<Xᵢ, ... Xₑ> ogólnego, w którym co najmniej jeden Aᵢ z następujących blokad:
    • 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 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 z Aᵢ do Bᵢ
  • Xᵢ jest kontrawariantny, a niejawna odwołanie lub konwersja tożsamości istnieje z Bᵢ do Aᵢ
  • Xᵢ jest niezmienna, a konwersja tożsamoś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 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 lub internal w interface_base interfejsu public . 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, ITextBoxi IListBox. Innymi słowy, IComboBox powyższy interfejs dziedziczy elementy członkowskie 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 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ągiem string[,]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, outi ref.
  • 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 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:

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 parametru T 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, aby E pochodziło z Dklasy , 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 in IListCounter jest niejednoznaczny. Jak pokazano w przykładzie, niejednoznaczność jest rozpoznawana przez rzutowanie x 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łanie n.Add(1.0) wybiera IDouble.Addwartość . 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łonka ILeft.F . Wywołanie d.F(1) wybiera ILeft.Fmetodę , mimo że IBase.F wydaje się, że nie jest ukryta w ścieżce dostępu prowadzącej przez IRightelement .

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 do ILeft do IBase ukrywa IBase.Felement członkowski jest również ukryty w ścieżce dostępu z IDerived 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 to IControl.Paint , a kwalifikowana nazwa elementu SetText to ITextBox.SetText. W powyższym przykładzie nie można odwoływać się do Paint 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, jak ICloneable.Clone i System.ICloneable.Clone są kwalifikowane nazwy składowe interfejsu Clone 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 , jak IControl i ITextBox.

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 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ący abstrakcję pliku, na przykład, prawdopodobnie implementuje funkcję składową Close , która ma wpływ na zwalnianie zasobu pliku, i implementuje Dispose metodę interfejsu IDisposable 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 bazowych Shape i nie jest podstawowym interfejsem ICloneable. Podobnie, w deklaracjach

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

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

deklaracja ICloneable.Clone w Ellipse 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 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:

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 interfejsu C.
  • Dodaj do L wszystkich podstawowych interfejsów interfejsów już w pliku L.
  • Usuń wszelkie duplikaty z pliku L.
  • Jeśli jakikolwiek możliwy typ skonstruowany utworzony na podstawie C metody, po zastąpieniu argumentów typu do Lelementu , powodują, że dwa interfejsy L są identyczne, oznacza to, że 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 X klasy lista L interfejsów składa się z l<U> elementów i I<V>. Deklaracja jest nieprawidłowa, ponieważ każdy skonstruowany typ i U 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 , Derivedponieważ 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. 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 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 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 elementu członkowskiego I.Minterfejsu , 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 zgodna I z elementem , Melement członkowski jest implementacją .I.M
  • W przeciwnym razie, jeśli S zawiera deklarację niestacjonanego publicznego elementu członkowskiego zgodnego Mz elementem , ten element członkowski jest implementacją elementu I.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, 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 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 i B są metodami, a nazwa, typ i listy parametrów A i B są identyczne.
  • A i B są właściwościami, nazwą i typem A i B są identyczne i A mają takie same metody dostępu jak B (A może mieć dodatkowe metody dostępu, jeśli nie jest to jawna implementacja elementu członkowskiego interfejsu).
  • A i B są zdarzeniami, a nazwa i typ A i B są identyczne.
  • A i B są indeksatorami, listami typów i parametrów A i B są identyczne i A mają takie same metody dostępu jak B (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łonkowski C 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 i IForm IControl są mapowane na metodę Paint w pliku 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 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 przez ITextBox, i IControl dziedziczone 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

interface Interface1
{
    void F();
}

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

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

metoda F w metodzie jest Class1 używana we Class2's wdrożeniu metody Interface1.

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 w TextBox obiekcie ukrywa metodę Paint w Controlmetodzie , ale nie zmienia mapowania Control.Paint metody na IControl.Paint, a wywołania Paint metody za pośrednictwem wystąpień klasy i wystąpień interfejsu będą miały następujące efekty

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

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ąpienie PaintControl 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 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 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 na Derived.Fmetody , Base.IMethods.G, Derived.IMethods.Hi Base.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 IBasejest również implementacja IDerived metody , mapowania 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:

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

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

W tym miejscu implementacja IMethods map F i G metod abstrakcyjnych, które są zastępowane w klasach nie abstrakcyjnych, które pochodzą z Cklasy .

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

przykład końcowy