Udostępnij za pomocą


19 Interfejsy

19.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ć różne rodzaje elementów członkowskich, zgodnie z opisem w §19.4. Sam interfejs może zapewnić implementację dla niektórych lub wszystkich elementów członkowskich funkcji, które deklaruje. Elementy członkowskie, dla których interfejs nie zapewnia implementacji, są abstrakcyjne. Ich implementacje muszą być dostarczane przez klasy lub struktury, które implementują interfejs lub interfejs pochodny, które zapewniają zastępowającą definicję.

Uwaga: Historycznie dodanie nowego elementu członkowskiego funkcji do interfejsu miało wpływ na wszystkich istniejących użytkowników tego typu interfejsu; była to przełomowa zmiana. Dodanie implementacji elementów członkowskich funkcji interfejsu pozwoliło deweloperom na uaktualnienie interfejsu, jednocześnie umożliwiając wszystkim implementatorom zastąpienie tej implementacji. Użytkownicy interfejsu mogą zaakceptować implementację jako zmianę powodującą niezgodność; jednak jeśli ich wymagania są inne, mogą zastąpić podane implementacje. notatka końcowa

19.2 Deklaracje interfejsu

19.2.1 Ogólne

Deklaracja interfejsu (interface_declaration) to deklaracja typu (type_declaration) (§14.7), która 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 (§23), a następnie opcjonalnego zestawu interface_modifiers (§19.2.2), a następnie opcjonalnego modyfikatora częściowego (§15.2.7), a następnie słowa kluczowego interface i identyfikatora, który nazywa interfejs, a następnie opcjonalną specyfikację variant_type_parameter_list (§19.2.3), a następnie opcjonalną specyfikację interface_base (§19.2.4)), a następnie opcjonalną specyfikację type_parameter_constraints_clause(§15.2.5), a następnie interface_body (§19.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).

19.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 (§24.2) jest dostępny tylko w niebezpiecznym kodzie (§24).

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.

19.2.3 Listy parametrów typu wariantu

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

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:

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.

19.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 S<Aᵢ,... 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 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 19.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ᵢ

Interfejsy podstawowe 19.2.4

Interfejs może dziedziczyć z zera lub większej liczby typów interfejsów, które są nazywane jawnym interfejsem podstawowyminterfejsu 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.

Interfejs pochodny może deklarować nowe elementy członkowskie, które ukrywają dziedziczone elementy członkowskie (§7.7.2.3) zadeklarowane w interfejsach podstawowych lub jawnie implementują dziedziczone elementy członkowskie (§19.6.2) zadeklarowane w interfejsach podstawowych.

interface_base
    : ':' interface_type_list
    ;

Jawne interfejsy podstawowe mogą być konstruowane typy interfejsów (§8.4, §19.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.

Interfejs podstawowy interfejsuto 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

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

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 w danych wyjściowych (§19.2.3.2).

Treść interfejsu 19.3

interface_body interfejsu definiuje elementy interfejsu.

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 Elementy członkowskie interfejsu

19.4.1 Ogólne

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

interface_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

Ta klauzula rozszerza opis składowych w klasach (§15.3) z ograniczeniami dla interfejsów. Elementy członkowskie interfejsu są deklarowane przy użyciu member_declarations z następującymi dodatkowymi regułami:

  • Niedozwolone jest finalizer_declaration .
  • Konstruktory wystąpień, constructor_declarations, są niedozwolone.
  • Wszystkie elementy członkowskie interfejsu niejawnie mają dostęp publiczny; jednak jawny modyfikator dostępu (§7.5.2) jest dozwolony z wyjątkiem konstruktorów statycznych (§15.12).
  • Modyfikator abstract jest dorozumiany dla elementów członkowskich funkcji interfejsu bez ciał; ten modyfikator może być jawnie podany.
  • Element członkowski funkcji wystąpienia interfejsu, którego deklaracja zawiera treść, jest niejawnie virtual elementem członkowskim, chyba że sealed jest używany modyfikator lub private . Modyfikator virtual może być jawnie podany.
  • Element private członkowski lub sealed funkcji interfejsu musi mieć treść.
  • Element private członkowski funkcji nie ma modyfikatora sealed.
  • Interfejs pochodny może zastąpić abstrakcyjny lub wirtualny element członkowski zadeklarowany w interfejsie podstawowym.
  • Jawnie zaimplementowany element członkowski funkcji nie ma modyfikatora sealed.

Niektóre deklaracje, takie jak constant_declaration (§15.4) nie mają żadnych ograniczeń w interfejsach.

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 członkowskiego nie jest traktowane jako błąd, ale powoduje ostrzeżenie (§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.

Uwaga: Składowe w klasie object nie są ściśle mówiąc członkami żadnego interfejsu (§19.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.

Przykład: rozważ interfejs IA z implementacją elementu członkowskiego M i właściwością P. Typ C implementowania nie zapewnia implementacji dla M elementu lub P. Muszą one być dostępne za pośrednictwem odwołania, którego typ czasu kompilacji jest interfejsem niejawnie konwertowanym na IA lub IB. Te elementy członkowskie nie są znajdowane za pośrednictwem wyszukiwania składowych w zmiennej typu C.

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

W interfejsach IA i IBelement członkowski M jest dostępny bezpośrednio według nazwy. Jednak w metodzie Mainnie można zapisać c.M() ani c.P, ponieważ te nazwy nie są widoczne. Aby je znaleźć, potrzebne są rzutowania do odpowiedniego typu interfejsu. Deklaracja w pliku M używa jawnej IB składni implementacji interfejsu. Jest to konieczne, aby ta metoda zastąpiła tę metodę w IAelemencie ; modyfikator override może nie być stosowany do elementu członkowskiego funkcji. przykład końcowy

19.4.2 Pola interfejsu

Ta klauzula rozszerza opis pól w klasach §15.5 dla pól zadeklarowanych w interfejsach.

Pola interfejsu są deklarowane przy użyciu field_declarations (§15.5.1) z następującymi dodatkowymi regułami:

  • Jest to błąd czasu kompilacji dla field_declaration zadeklarowania pola wystąpienia.

Przykład: Następujący program zawiera statyczne elementy członkowskie różnych rodzajów:

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

Generowane dane wyjściowe są następujące:

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

przykład końcowy

Aby uzyskać informacje dotyczące alokacji i inicjowania pól statycznych, zobacz §19.4.8 .

Metody interfejsu 19.4.3

Ta klauzula rozszerza opis metod w klasach §15.6 dla metod zadeklarowanych w interfejsach.

Metody interfejsu są deklarowane przy użyciu method_declarations (§15.6)). Atrybuty, return_type, ref_return_type, identyfikator i parameter_list deklaracji metody interfejsu mają takie samo znaczenie jak te deklaracji metody w klasie. Metody interfejsu mają następujące dodatkowe reguły:

  • method_modifier nie obejmuje override.

  • Metoda, której treść jest średnikiem (;) jest abstract; abstract modyfikator nie jest wymagany, ale jest dozwolony.

  • Deklaracja metody interfejsu, która ma treść bloku lub treść wyrażenia jako method_body jest virtual; virtual modyfikator nie jest wymagany, ale jest dozwolony.

  • Method_declaration nie ma type_parameter_constraints_clause, chyba że ma również type_parameter_list.

  • Lista wymagań dotyczących prawidłowych kombinacji modyfikatorów określonych dla metody klasy jest rozszerzona w następujący sposób:

    • Deklaracja statyczna, która nie jest extern, ma treść bloku lub treść wyrażenia jako method_body.
    • Wirtualna deklaracja, która nie jest extern, ma treść blokową lub treść wyrażenia jako method_body.
    • Prywatna deklaracja, która nie jest extern, ma ciało blokowe lub wyrażenie jako method_body.
    • Zapieczętowana deklaracja, która nie jest extern, ma treść bloku lub treść wyrażenia jako method_body.
    • Deklaracja asynchronizna musi zawierać treść bloku lub treść wyrażenia jako method_body.
  • Wszystkie typy parametrów metody interfejsu powinny być bezpieczne dla danych wejściowych (§19.2.3.2), a zwracany typ musi być bezpieczny void lub wyjściowy.

  • 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

  • Każde ograniczenie typu klasy, ograniczenie typu interfejsu i ograniczenie parametru typu dla dowolnego typu parametrów metody musi być 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 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:

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

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

Uwaga: Zobacz przykład §19.4.2 , który nie tylko pokazuje metodę statyczną z implementacją, ale jako wywoływaną Main metodę i ma odpowiedni typ zwracany i podpis, jest to również punkt wejścia. notatka końcowa

Metoda wirtualna z implementacją zadeklarowaną w interfejsie może zostać zastąpiona jako abstrakcyjna w interfejsie pochodnym. Jest to nazywane reabstrakcją.

Przykład:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

Jest to przydatne w interfejsach pochodnych, w których implementacja metody jest nieodpowiednia, a bardziej odpowiednia implementacja powinna być dostarczana przez implementację klas. przykład końcowy

Właściwości interfejsu 19.4.4

Ta klauzula rozszerza opis właściwości w klasach §15.7 dla właściwości zadeklarowanych w interfejsach.

Właściwości interfejsu są deklarowane przy użyciu property_declarations (§15.7.1) z następującymi dodatkowymi regułami:

  • property_modifier nie obejmuje override.

  • Implementacja jawnego elementu członkowskiego interfejsu nie zawiera accessor_modifier (§15.7.3).

  • Interfejs pochodny może jawnie implementować abstrakcyjną właściwość interfejsu zadeklarowaną w interfejsie podstawowym.

    Uwaga: ponieważ interfejs nie może zawierać pól wystąpienia, właściwość interfejsu nie może być właściwością automatyczną wystąpienia, ponieważ wymagałoby to deklaracji niejawnych pól ukrytych wystąpień. notatka końcowa

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

  • Deklaracja metody interfejsu, która ma treść bloku lub treść wyrażenia jako method_body jest virtual; virtual modyfikator nie jest wymagany, ale jest dozwolony.

  • Wystąpienie property_declaration , które nie ma implementacji, jest abstract; abstract modyfikator nie jest wymagany, ale jest dozwolony. Nigdy nie jest uważana za automatycznie zaimplementowaną właściwość (§15.7.4).

Zdarzenia interfejsu 19.4.5

Ta klauzula rozszerza opis zdarzeń w klasach §15.8 dla zdarzeń zadeklarowanych w interfejsach.

Zdarzenia interfejsu są deklarowane przy użyciu event_declarations (§15.8.1), z następującymi dodatkowymi regułami:

  • event_modifier nie obejmuje override.
  • Interfejs pochodny może implementować zdarzenie interfejsu abstrakcyjnego zadeklarowane w interfejsie podstawowym (§15.8.5).
  • Jest to błąd czasu kompilacji dla variable_declarators w wystąpieniu event_declaration zawierać dowolne variable_initializers.
  • Zdarzenie wystąpienia z modyfikatorami virtual lub sealed musi zadeklarować metody dostępu. Nigdy nie jest uważany za automatycznie zaimplementowane zdarzenie podobne do pola (§15.8.2).
  • Zdarzenie wystąpienia z modyfikatorem abstract nie może deklarować metod dostępu.
  • Typ zdarzenia interfejsu musi być bezpieczny dla danych wejściowych.

Indeksatory interfejsu 19.4.6

Ta klauzula rozszerza opis indeksatorów w klasach §15.9 dla indeksatorów zadeklarowanych w interfejsach.

Indeksatory interfejsu są deklarowane przy użyciu indexer_declarations (§15.9) z następującymi dodatkowymi regułami:

  • indexer_modifier nie obejmuje override.

  • Indexer_declaration, który ma treść wyrażenia lub zawiera metodę dostępu z treścią bloku lub treścią wyrażenia, jest virtual; virtual modyfikator nie jest wymagany, ale jest dozwolony.

  • Indexer_declaration, którego ciała dostępu są średnikami (;) jest abstract; abstract modyfikator nie jest wymagany, ale jest dozwolony.

  • Wszystkie typy parametrów indeksatora interfejsu powinny być bezpieczne dla danych wejściowych (§19.2.3.2).

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

Operatory interfejsu 19.4.7

Ta klauzula rozszerza opis składowych operator_declaration w klasach §15.10 dla operatorów zadeklarowanych w interfejsach.

Operator_declaration w interfejsie to implementacja (§19.1).

Jest to błąd czasu kompilacji dla interfejsu w celu zadeklarowania operatora konwersji, równości lub nierówności.

19.4.8 Konstruktory statyczne interfejsu

Ta klauzula rozszerza opis konstruktorów statycznych w klasach §15.12 dla konstruktorów statycznych zadeklarowanych w interfejsach.

Konstruktor statyczny zamkniętego interfejsu (§8.4.3) jest wykonywany co najwyżej raz w danej domenie aplikacji. Wykonanie konstruktora statycznego jest wyzwalane przez pierwsze z następujących akcji, które mają być wykonywane w domenie aplikacji:

  • Do których odwołuje się dowolny statyczny element członkow interfejsu.
  • Przed wywołaniem Main metody dla interfejsu zawierającego Main metodę (§7.1), w której rozpoczyna się wykonywanie.
  • Ten interfejs zapewnia implementację elementu członkowskiego i jest on dostępny jako najbardziej konkretna implementacja (§19.4.10) dla tego elementu członkowskiego.

Uwaga: w przypadku, gdy żadne z powyższych akcji nie ma miejsca, konstruktor statyczny interfejsu może nie być wykonywany dla programu, w którym wystąpienia typów implementujących interfejs są tworzone i używane. notatka końcowa

Aby zainicjować nowy typ zamkniętego interfejsu, najpierw zostanie utworzony nowy zestaw pól statycznych dla tego określonego typu zamkniętego. Każde z pól statycznych jest inicjowane do wartości domyślnej. Następnie inicjatory pól statycznych są wykonywane dla tych pól statycznych. Na koniec jest wykonywany konstruktor statyczny.

Uwaga: zobacz §19.4.2 , aby zapoznać się z przykładem użycia różnych rodzajów statycznych elementów członkowskich (w tym metody Main) zadeklarowanych w interfejsie. notatka końcowa

19.4.9 Typy zagnieżdżone interfejsu

Ta klauzula rozszerza opis zagnieżdżonych typów w klasach §15.3.9 dla typów zagnieżdżonych zadeklarowanych w interfejsach.

Jest to błąd deklarowania typu klasy, typu struktury lub typu wyliczenia w zakresie parametru typu, który został zadeklarowany przy użyciu variance_annotation (§19.2.3.1).

Przykład: Deklaracja C poniżej jest błędem.

interface IOuter<out T>
{
    class C { } // error: class declaration within scope of variant type parameter 'T'
}

przykład końcowy

19.4.10 najbardziej specyficzna implementacja

Każda klasa i struktura mają najbardziej specyficzną implementację dla każdego wirtualnego elementu członkowskiego zadeklarowanego we wszystkich interfejsach implementowanych przez ten typ wśród implementacji występujących w typie lub jego interfejsach bezpośrednich i pośrednich. Najbardziej specyficzna implementacja to unikatowa implementacja, która jest bardziej specyficzna niż każda inna implementacja.

Uwaga: Najbardziej specyficzna reguła implementacji gwarantuje, że niejednoznaczność wynikająca z dziedziczenia interfejsu diamentowego jest jawnie rozpoznawana przez programistę w momencie wystąpienia konfliktu. notatka końcowa

W przypadku typu T , który jest strukturą lub klasą, która implementuje interfejsy I2 i I3, gdzie I2 i I3 oba typy pochodzą bezpośrednio lub pośrednio z interfejsu I , który deklaruje element członkowski M, najbardziej specyficzną implementacją M jest:

  • Jeśli T deklaruje implementację I.Mprogramu , implementacja ta jest najbardziej konkretną implementacją.
  • W przeciwnym razie, jeśli T jest klasą, a bezpośrednia lub pośrednia klasa bazowa deklaruje implementację I.Mklasy , najbardziej pochodną klasą T bazową jest najbardziej specyficzna implementacja.
  • W przeciwnym razie, jeśli interfejsy i I2 są implementowane przez I3 program i T pochodzą bezpośrednio I3 lub pośrednio, I2 jest bardziej szczegółową implementacją niż I3.M.I2.M
  • W przeciwnym razie ani I2.MI3.M nie są bardziej szczegółowe i występuje błąd.

Przykład:

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB : IA
{
    void IA.M() { Console.WriteLine("IB.M"); }
}

interface IC: IA
{
    void IA.M() { Console.WriteLine("IC.M"); }
}

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

Najbardziej specyficzna reguła implementacji gwarantuje, że konflikt (tj. niejednoznaczność wynikająca z dziedziczenia diamentów) jest jawnie rozpoznawana przez programistę w momencie wystąpienia konfliktu. przykład końcowy

19.4.11 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.12.4) wyrażeń formularza I.M i I[A], gdzie I jest typem interfejsu, jest stałą, M polem, metodą, właściwością lub zdarzeniem tego typu interfejsu i A jest listą argumentów indeksatora.

W klasie D, z bezpośrednią lub pośrednią klasą Bbazową , gdzie B bezpośrednio lub pośrednio implementuje interfejs I i I definiuje metodę M(), wyrażenie base.M() jest prawidłowe tylko wtedy, gdy base.M() statycznie (§12.3) wiąże się z implementacją M() typu klasy.

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.10.2) i dostęp indeksatora (§12.8.12.4) 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 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

interface IList
{
    int Count { get; set; }
}

interface ICounter
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

pierwsza instrukcja powoduje błąd czasu kompilacji, ponieważ wyszukiwanie członka (§12.5) w 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

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

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

19.5 Kwalifikowane nazwy składowych 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

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:

namespace GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

W przestrzeni nazw GraphicsLib zarówno IPolygon.CalculateArea, jak i GraphicsLib.IPolygon.CalculateArea są zakwalifikowane jako nazwy składowe interfejsu dla metody CalculateArea.

przykład końcowy

Implementacje interfejsu 19.6

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

Klasa lub struktura C , która implementuje interfejs I , musi zapewniać lub dziedziczyć implementację dla każdego elementu członkowskiego zadeklarowanego w I tym celu C . Publiczni I członkowie programu mogą być definiowani w publicznych członkach programu C. Niepubliczne elementy członkowskie zadeklarowane w programie I , które są dostępne w programie C , mogą być zdefiniowane przy C użyciu jawnej implementacji interfejsu (§19.6.2).

Element członkowski w typie pochodnym, który spełnia mapowanie interfejsu (§19.6.5), ale nie implementuje pasującego elementu członkowskiego interfejsu podstawowego wprowadza nowy element członkowski. Dzieje się tak, gdy jawna implementacja interfejsu jest wymagana do zdefiniowania elementu członkowskiego interfejsu.

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 dziedziczące z C również implementują ten interfejs niejawnie.

Interfejsy podstawowe określone w deklaracji klasy mogą być konstruowane typy interfejsów (§8.4, §19.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 §19.6.3.

19.6.2 Implementacje jawnych elementów członkowskich interfejsu

Do celów implementowania interfejsów, klasy, struktury lub interfejsu może deklarować 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. Klasa lub struktura, która implementuje element członkowski inny niż publiczny w interfejsie podstawowym, musi zadeklarować jawną implementację składową interfejsu. Interfejs implementujący element członkowski w interfejsie podstawowym musi zadeklarować jawną implementację elementu członkowskiego interfejsu.

Element członkowski interfejsu pochodnego, który spełnia mapowanie interfejsu (§19.6.5) ukrywa podstawowy element członkowski interfejsu (§7.7.2). Kompilator wydaje ostrzeżenie, chyba że new modyfikator jest obecny.

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

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 elementu członkowskiego wystąpienia interfejsu może być dostępna tylko za pośrednictwem wystąpienia interfejsu i jest w tym przypadku przywoływane po prostu przez jego nazwę elementu członkowskiego. Dostęp do statycznej implementacji statycznej składowej interfejsu można uzyskać tylko za pośrednictwem nazwy interfejsu.

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.

Implementacja metody interfejsu jawnego dziedziczy wszelkie ograniczenia parametrów typu z interfejsu.

Type_parameter_constraints_clause implementacji metody jawnego interfejsu może składać się tylko z class lub structprimary_constraint zastosowanych do type_parameter, które są znane na podstawie odziedziczonych ograniczeń jako odpowiednio typy referencyjne lub wartościowe. Dowolny typ formularza T? w podpisie implementacji metody jawnego interfejsu, gdzie T jest parametrem typu, jest interpretowany w następujący sposób:

  • class Jeśli ograniczenie jest dodawane dla parametru typu T, T? jest typem odwołania dopuszczającym wartość null; w przeciwnym razie
  • Jeśli nie ma żadnego dodanego ograniczenia lub zostanie dodane ograniczenie struct, to dla parametru typu T, T? jest typem wartości, który może przyjmować wartość null.

Przykład: Poniżej pokazano, jak działają reguły, gdy są zaangażowane parametry typu:

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

Bez ograniczenia where T : classparametru typu metoda podstawowa z parametrem typu odwołania nie może zostać zastąpiona. przykład końcowy

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 byłoby niemożliwe, aby klasa, struktura lub interfejs miał różne implementacje składowych interfejsu z tym samym typem podpisu i zwracania, co byłoby niemożliwe dla klasy, struktury lub interfejsu 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, struktura lub interfejs nazywa interfejs w swojej klasie bazowej lub liście interfejsu podstawowego, 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 implementacji jawnej składowej 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

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

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

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

19.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 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:

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> (§19.6.7).

19.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 ograniczenia C.F<T>, ponieważ T: 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:

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, §19.6.2). notatka końcowa

Mapowanie interfejsu 19.6.5

Klasa lub struktura zapewnia implementacje wszystkich abstrakcyjnych 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 element członkowski M jest zadeklarowany, jest określana przez sprawdzenie każdej klasy, interfejsu lub struktury S, począwszy od C i powtarzania dla każdej kolejnej klasy bazowej Ci zaimplementowany interfejs elementu , dopóki dopasowanie nie zostanie zlokalizowane:

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

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

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

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

przykład końcowy

Do celów mapowania interfejsu, klasa, interfejs lub składowa A 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

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:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

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

W tym miejscu metody zarówno Paint jak i IControl są mapowane na metodę IForm w Paint. 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:

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.

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

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

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.

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

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

przykład końcowy

Ponowne wdrożenie interfejsu 19.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 członków oraz dziedziczone jawne deklaracje członków interfejsu biorą udział 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 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:

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

19.6.8 Klasy abstrakcyjne i interfejsy

Podobnie jak klasa nie abstrakcyjna, klasa abstrakcyjna zapewnia implementacje wszystkich abstrakcyjnych 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 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:

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