Поделиться через


18 Интерфейсов

18.1 Общие

Интерфейс определяет контракт. Класс или структура, реализующая интерфейс, должна соответствовать своему контракту. Интерфейс может наследовать от нескольких базовых интерфейсов, а класс или структура может реализовать несколько интерфейсов.

Интерфейсы могут содержать методы, свойства, события и индексаторы. Сам интерфейс не предоставляет реализации для членов, объявленных им. Интерфейс просто указывает элементы, которые должны предоставляться классами или структурами, реализующими интерфейс.

Объявления интерфейса 18.2

18.2.1 Общие

Interface_declaration — это type_declaration (§14.7), которая объявляет новый тип интерфейса.

interface_declaration
    : attributes? interface_modifier* 'partial'? 'interface'
      identifier variant_type_parameter_list? interface_base?
      type_parameter_constraints_clause* interface_body ';'?
    ;

Interface_declaration состоит из необязательного набора атрибутов (§22), за которым следует необязательный набор interface_modifiers (§18.2.2), а затем необязательный частичный модификатор (§15.2.7), за которым следует ключевое слово и идентификатор, который называет интерфейс, за которым следует дополнительная спецификация variant_type_parameter_list (interface§18.2.3), а затем необязательный interface_base спецификация (§18.2.4), за которой следует необязательная спецификация type_parameter_constraints_clause(§15.2.5), за которой следует interface_body (§18.3), за которым следует точка с запятой.

Объявление интерфейса не должно предоставлять type_parameter_constraints_clause, если он также не предоставляет variant_type_parameter_list.

Объявление интерфейса, которое предоставляет variant_type_parameter_list , является универсальным объявлением интерфейса. Кроме того, любой интерфейс, вложенный в объявление универсального класса или универсальное объявление структуры, является универсальным объявлением интерфейса, так как аргументы типа для содержащего типа должны быть предоставлены для создания созданного типа (§8.4).

Модификаторы интерфейса 18.2.2

Interface_declaration может включать в себя последовательность модификаторов интерфейса:

interface_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) доступен только в небезопасном коде (§23).

Это ошибка во время компиляции для одного модификатора, который будет отображаться несколько раз в объявлении интерфейса.

Модификатор new разрешен только в интерфейсах, определенных в классе. Он указывает, что интерфейс скрывает унаследованный элемент по тому же имени, как описано в разделе 15.3.5.

Модификаторы publicи , protectedinternalprivate а также управляют специальными возможностями интерфейса. В зависимости от контекста, в котором происходит объявление интерфейса, может быть разрешено только некоторые из этих модификаторов (§7.5.2). Если объявление частичного типа (§15.2.7) содержит спецификацию специальных возможностей (с помощью модификаторов , publicи ,protectedinternal), применяются правила в private.

Списки параметров типа 18.2.3

18.2.3.1 Общие

Списки параметров типа variant могут возникать только в типах интерфейса и делегатов. Отличие от обычных type_parameter_list s является необязательным variance_annotation для каждого параметра типа.

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

Если заметка о дисперсии имеет значение out, параметр типа считается ковариантным. Если заметка о дисперсии имеет inзначение, параметр типа считается контравариантным. Если заметки о дисперсии отсутствуют, параметр типа считается инвариантным.

Пример. В следующем примере:

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

X является ковариантным, Y является контравариантным и Z инвариантным.

пример конца

Если универсальный интерфейс объявлен в нескольких частях (§15.2.3), каждое частичное объявление должно указывать одинаковые дисперсии для каждого параметра типа.

18.2.3.2 Вариативная безопасность

Вхождение заметок дисперсии в списке параметров типа ограничивает места, в которых типы могут находиться в объявлении типа.

Тип T небезопасн, если одно из следующих удержаний:

  • T является параметром типа contravariant
  • T — это тип массива с типом элемента, небезопасным для вывода
  • T — это интерфейс или тип Sᵢ,... Aₑ делегата, построенный из универсального типа S<Xᵢ, ... Xₑ> , где для одного Aᵢ из следующих удержаний:
    • Xᵢ является ковариантным или инвариантным и Aᵢ небезопасным для выходных данных.
    • Xᵢ является контравариантным или инвариантным и Aᵢ небезопасным для ввода.

Тип T является небезопасным для ввода, если одно из следующих удержаний:

  • T — параметр ковариантного типа
  • T — это тип массива с типом элемента, небезопасным для ввода
  • T — это интерфейс или тип S<Aᵢ,... Aₑ> делегата, построенный из универсального типа S<Xᵢ, ... Xₑ> , где для одного Aᵢ из следующих удержаний:
    • Xᵢ является ковариантным или инвариантным и Aᵢ является небезопасным для ввода.
    • Xᵢ является контравариантным или инвариантным и Aᵢ небезопасным для выходных данных.

Интуитивно понятно, выходной тип запрещен в выходной позиции, а входной небезопасный тип запрещен в входной позиции.

Тип является выходным, если он не является выходным, небезопасным, и входобезопасным, если он не является входным.

Преобразование вариативности 18.2.3.3

Назначение заметок дисперсии заключается в том, чтобы обеспечить более ленивые (но по-прежнему типобезопасные) преобразования в типы интерфейсов и делегатов. Для этого определения неявных (§10.2) и явных преобразований (§10.3) используют понятие вариативности преобразования, которое определяется следующим образом:

Тип T<Aᵢ, ..., Aᵥ> является преобразуемым в тип T<Bᵢ, ..., Bᵥ> , если T является интерфейсом или типом делегата, объявленным с параметрами T<Xᵢ, ..., Xᵥ>типа варианта, и для каждого параметра Xᵢ типа варианта один из следующих удержаний:

  • Xᵢявляется ковариантным, а неявная ссылка или преобразование удостоверений существует из AᵢBᵢ
  • Xᵢявляется контравариантным, а неявная ссылка или преобразование удостоверений существует из BᵢAᵢ
  • Xᵢявляется инвариантным, а преобразование удостоверений существует из AᵢBᵢ

Базовые интерфейсы 18.2.4

Интерфейс может наследовать от нуля или нескольких типов интерфейсов, которые называются явными базовыми интерфейсами интерфейса. Если интерфейс имеет один или несколько явных базовых интерфейсов, то в объявлении этого интерфейса за идентификатором интерфейса следует двоеточие и список типов базовых интерфейсов с разделителем запятыми.

interface_base
    : ':' interface_type_list
    ;

Явные базовые интерфейсы можно создать типы интерфейсов (§8.4, §18.2). Базовый интерфейс не может быть параметром типа самостоятельно, хотя он может включать параметры типа, которые находятся в области.

Для созданного типа интерфейса явные базовые интерфейсы формируются путем принятия явных объявлений базового интерфейса в объявлении универсального типа и подстановки для каждого type_parameter в объявлении базового интерфейса, соответствующего type_argument созданного типа.

Явные базовые интерфейсы интерфейса должны быть по крайней мере так же доступны, как и сам интерфейс (§7.5.5).

Примечание. Например, это ошибка во время компиляции для указания private или internal интерфейса в interface_basepublic интерфейса. конечная заметка

Это ошибка во время компиляции для интерфейса, который напрямую или косвенно наследуется от себя.

Базовые интерфейсы интерфейса — это явные базовые интерфейсы и их базовые интерфейсы. Другими словами, набор базовых интерфейсов — это полное транзитивное закрытие явных базовых интерфейсов, их явных базовых интерфейсов и т. д. Интерфейс наследует все члены своих базовых интерфейсов.

Пример. В следующем коде

interface IControl
{
    void Paint();
}

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

interface IListBox : IControl
{
    void SetItems(string[] items);
}

interface IComboBox: ITextBox, IListBox {}

базовые интерфейсыIComboBox: IControlи ITextBoxIListBox. Другими словами, приведенный IComboBox выше интерфейс наследует члены SetText , а SetItems также Paint.

пример конца

Элементы, унаследованные от созданного универсального типа, наследуются после подстановки типов. То есть все составляющие типы в члене имеют параметры типа объявления базового класса, замененные соответствующими аргументами типа, используемыми в спецификации class_base .

Пример. В следующем коде

interface IBase<T>
{
    T[] Combine(T a, T b);
}

interface IDerived : IBase<string[,]>
{
    // Inherited: string[][,] Combine(string[,] a, string[,] b);
}

интерфейс IDerived наследует Combine метод после замены Tпараметра string[,] типа.

пример конца

Класс или структура, реализующая интерфейс, также неявно реализует все базовые интерфейсы интерфейса.

Обработка интерфейсов в нескольких частях объявления частичного интерфейса (§15.2.7) рассматривается далее в §15.2.4.3.

Каждый базовый интерфейс интерфейса должен быть выходным (§18.2.3.2).

Текст интерфейса 18.3

Interface_body интерфейса определяет элементы интерфейса.

interface_body
    : '{' interface_member_declaration* '}'
    ;

Элементы интерфейса 18.4

18.4.1 Общие

Члены интерфейса являются элементами, унаследованными от базовых интерфейсов и членов, объявленных самим интерфейсом.

interface_member_declaration
    : interface_method_declaration
    | interface_property_declaration
    | interface_event_declaration
    | interface_indexer_declaration
    ;

Объявление интерфейса объявляет ноль или больше элементов. Члены интерфейса должны быть методами, свойствами, событиями или индексаторами. Интерфейс не может содержать константы, поля, операторы, конструкторы экземпляров, методы завершения или типы, а также не может содержать статические элементы любого типа.

Все члены интерфейса неявно имеют открытый доступ. Это ошибка во время компиляции для объявлений членов интерфейса для включения всех модификаторов.

Interface_declaration создает новое пространство объявления (§7.3), а параметры типа и interface_member_declarationсразу же, содержащиеся в interface_declaration, вводят новые члены в это пространство объявлений. Следующие правила применяются к interface_member_declarations:

  • Имя параметра типа в variant_type_parameter_list объявления интерфейса должно отличаться от имен всех остальных параметров типа в одном variant_type_parameter_list и должно отличаться от имен всех членов интерфейса.
  • Имя метода должно отличаться от имен всех свойств и событий, объявленных в одном интерфейсе. Кроме того, подпись (§7.6) метода должна отличаться от сигнатур всех других методов, объявленных в одном интерфейсе, и два метода, объявленные в одном интерфейсе, не должны иметь сигнатуры, которые отличаются исключительно по in, outи ref.
  • Имя свойства или события должно отличаться от имен всех остальных членов, объявленных в одном интерфейсе.
  • Подпись индексатора должна отличаться от подписей всех остальных индексаторов, объявленных в одном интерфейсе.

Наследуемые элементы интерфейса в частности не являются частью пространства объявления интерфейса. Таким образом, интерфейс может объявлять член с тем же именем или сигнатурой, что и унаследованный элемент. Когда это происходит, производный член интерфейса, как сообщается , скрывает базовый элемент интерфейса. Скрытие унаследованного элемента не считается ошибкой, но это приводит к возникновению предупреждения компилятором. Чтобы отключить предупреждение, объявление производного элемента интерфейса должно включать new модификатор, указывающий, что производный элемент предназначен для скрытия базового элемента. Этот раздел рассматривается далее в разделе 7.7.2.3.

new Если модификатор включен в объявление, которое не скрывает унаследованный элемент, предупреждение будет выдано для этого эффекта. Это предупреждение подавляется путем удаления модификатора new .

Примечание. Члены класса object не являются, строго говоря, членами любого интерфейса (§18.4). Однако члены класса object доступны с помощью подстановки элементов в любом типе интерфейса (§12.5). конечная заметка

Набор элементов интерфейса, объявленного в нескольких частях (§15.2.7), является объединением членов, объявленных в каждой части. Тела всех частей объявления интерфейса имеют одно и то же пространство объявления (§7.3), а область каждого элемента (§7.7) распространяется на тела всех частей.

Методы интерфейса 18.4.2

Методы интерфейса объявляются с помощью 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* ';'
    ;

Атрибуты, return_type, ref_return_type, идентификатор и parameter_list объявления метода интерфейса имеют то же значение, что и объявление метода в классе (§15.6). Объявление метода интерфейса не допускается указывать текст метода, поэтому объявление всегда заканчивается точкой с запятой.

Все типы параметров метода интерфейса должны быть входобезопасны (§18.2.3.2), а возвращаемый тип должен быть void либо выходным, либо выходным. Кроме того, все типы выходных или ссылочных параметров также должны быть выходными.

Примечание. Выходные параметры должны быть входобезопасны из-за распространенных ограничений реализации. конечная заметка

Кроме того, каждое ограничение типа класса, ограничение типа интерфейса и ограничение параметра типа для любых параметров типа метода должно быть безопасным для ввода.

Кроме того, каждое ограничение типа класса, ограничение типа интерфейса и ограничение параметра типа для любого параметра типа метода должно быть безопасным для ввода.

Эти правила гарантируют, что любое ковариантное или контравариантное использование интерфейса остается типобезопасным.

Пример:

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

недоформен, так как использование T в качестве ограничения U параметра типа не является безопасным для ввода.

Если это ограничение не было бы на месте, можно было бы нарушить безопасность типов следующим образом:

class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<U>() {...} 
}

...

I<B> b = new C();
b.M<E>();

Это на самом деле вызов C.M<E>. Но этот вызов требует, чтобы E производный от D, поэтому безопасность типов будет нарушена здесь.

пример конца

Свойства интерфейса 18.4.3

Свойства интерфейса объявляются с помощью interface_property_declaration:

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

Атрибуты, тип и идентификатор объявления свойства интерфейса имеют то же значение, что и объявление свойства в классе (§15.7).

Методы доступа объявления свойства интерфейса соответствуют методу доступа объявления свойства класса (§15.7.3), за исключением того, что accessor_body всегда должен быть точкой с запятой. Таким образом, методы доступа просто указывают, является ли свойство доступным только для чтения, записи или только для чтения.

Тип свойства интерфейса должен быть выходным, если есть метод доступа get, и должен быть входобезопасным, если есть набор доступа.

События интерфейса 18.4.4

События интерфейса объявляются с помощью interface_event_declaration:

interface_event_declaration
    : attributes? 'new'? 'event' type identifier ';'
    ;

Атрибуты, тип и идентификатор объявления события интерфейса имеют то же значение, что и объявление события в классе (§15.8).

Тип события интерфейса должен быть безопасным для ввода.

Индексаторы интерфейсов 18.4.5

Индексаторы интерфейса объявляются с помощью interface_indexer_declaration:

interface_indexer_declaration
    : attributes? 'new'? type 'this' '[' parameter_list ']'
      '{' interface_accessors '}'
    | attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
      '{' ref_interface_accessor '}'
    ;

Атрибуты, тип и parameter_list объявления индексатора интерфейса имеют то же значение, что и объявление индексатора в классе (§15.9).

Методы доступа объявления индексатора интерфейса соответствуют методу доступа объявления индексатора класса (§15.9), за исключением того, что accessor_body всегда должен быть точкой с запятой. Таким образом, методы доступа просто указывают, является ли индексатор доступен только для чтения, записи или только для чтения.

Все типы параметров индексатора интерфейса должны быть безопасными для ввода (§18.2.3.2). Кроме того, все типы выходных или ссылочных параметров также должны быть выходными.

Примечание. Выходные параметры должны быть входобезопасны из-за распространенных ограничений реализации. конечная заметка

Тип индексатора интерфейса должен быть выходным, если имеется метод доступа get, и должен быть входобезопасным, если есть набор доступа.

Доступ к члену интерфейса 18.4.6

Доступ к элементам интерфейса осуществляется через доступ к членам (§12.8.7) и доступ индексатора (§12.8.12.3) выражений формы I.M и I[A], где I является типом интерфейса, M — это метод, свойство или событие этого типа интерфейса, а A — список аргументов индексатора.

Для интерфейсов, которые применяют строгое однонаследование (каждый интерфейс в цепочке наследования имеет ровно ноль или один прямой базовый интерфейс), эффекты поиска элементов (§12.5), вызова методов (§12.8.10.2) и доступа к индексаторам (§12.8.12.3) точно такие же, как и для классов и структур: члены более производных классов скрывают менее производные члены с тем же именем или сигнатурой. Однако для интерфейсов с несколькими наследованиеми неоднозначность может возникать, когда два или более несвязанных базовых интерфейсов объявляют члены с одинаковым именем или подписью. В этом подклаузе показаны несколько примеров, некоторые из которых приводят к неоднозначности и другим, которые не делают. Во всех случаях явные приведения можно использовать для устранения неоднозначности.

Пример. В следующем коде

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

Первые две инструкции вызывают ошибки во время компиляции, так как поиск члена (§12.5) Count является IListCounter неоднозначным. Как показано в примере, неоднозначность разрешается путем приведения x к соответствующему типу базового интерфейса. Такие приведения не имеют затрат на время выполнения— они просто состоят из просмотра экземпляра как менее производного типа во время компиляции.

пример конца

Пример. В следующем коде

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

вызов n.Add(1) выбирается путем применения правил разрешения перегрузки IInteger.Add в §12.6.4. Аналогичным образом, вызов n.Add(1.0) выбирается IDouble.Add. При вставке явных приведения существует только один метод кандидата и, следовательно, нет неоднозначности.

пример конца

Пример. В следующем коде

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

IBase.F элемент скрыт элементомILeft.F. Вызов d.F(1) таким образом выбирается ILeft.F, хотя IBase.F , как представляется, не скрыт в пути доступа, который ведет через IRight.

Интуитивно понятное правило скрытия в интерфейсах с несколькими наследованиеми просто так: если элемент скрыт в любом пути доступа, он скрыт во всех путях доступа. Так как путь доступа от IDerivedILeftIBase к скрытию IBase.F, член также скрыт в пути доступа к IDerivedIRightIBase.

пример конца

18.5 Квалифицированные имена элементов интерфейса

Иногда член интерфейса ссылается на его полное имя элемента интерфейса. Полное имя члена интерфейса состоит из имени интерфейса, в котором объявляется член, а затем точка, за которой следует имя члена. Полное имя члена ссылается на интерфейс, в котором объявлен элемент.

Пример. Учитывая объявления

interface IControl
{
    void Paint();
}

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

полное имя Paint и IControl.Paint полное имя SetText ITextBox.SetText. В приведенном выше примере невозможно ссылаться на Paint него ITextBox.Paint.

пример конца

Если интерфейс является частью пространства имен, полное имя члена интерфейса может включать имя пространства имен.

Пример:

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

System В пространстве ICloneable.Clone имен оба и System.ICloneable.Clone являются полными именами элементов интерфейса для Clone метода.

пример конца

Реализации интерфейса 18.6

18.6.1 Общие

Интерфейсы могут быть реализованы классами и структурами. Чтобы указать, что класс или структура напрямую реализует интерфейс, интерфейс включается в список базовых классов класса или структуры.

Пример:

interface ICloneable
{
    object Clone();
}

interface IComparable
{
    int CompareTo(object other);
}

class ListEntry : ICloneable, IComparable
{
    public object Clone() {...}    
    public int CompareTo(object other) {...}
}

пример конца

Класс или структура, которая непосредственно реализует интерфейс, также неявно реализует все базовые интерфейсы интерфейса. Это верно, даже если класс или структура явно не перечисляют все базовые интерфейсы в списке базовых классов.

Пример:

interface IControl
{
    void Paint();
}

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

class TextBox : ITextBox
{
    public void Paint() {...}
    public void SetText(string text) {...}
}

Здесь класс TextBox реализует как IControl , так и ITextBox.

пример конца

Когда класс C напрямую реализует интерфейс, все классы, производные от C этого, также реализуют интерфейс неявно.

Базовые интерфейсы, указанные в объявлении класса, можно создать типы интерфейсов (§8.4, §18.2).

Пример. В следующем коде показано, как класс может реализовать созданные типы интерфейсов:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

пример конца

Базовые интерфейсы объявления универсального класса должны соответствовать правилу уникальности, описанному в §18.6.3.

Реализация элементов интерфейса 18.6.2

Для реализации интерфейсов класс или структура могут объявлять явные реализации элементов интерфейса. Явная реализация элемента интерфейса — это объявление метода, свойства, события или индексатора, ссылающееся на полное имя члена интерфейса.

Пример:

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) {...}
}

Здесь IDictionary<int,T>.this и IDictionary<int,T>.Add есть явные реализации элементов интерфейса.

пример конца

Пример. В некоторых случаях имя члена интерфейса может не соответствовать реализации класса, в этом случае член интерфейса может быть реализован с помощью явной реализации элемента интерфейса. Класс, реализующий абстракции файлов, например, может реализовать Close функцию-член, которая имеет эффект освобождения файлового ресурса, и реализовать Dispose метод интерфейса с помощью явной IDisposable реализации элемента интерфейса:

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);
    }
}

пример конца

Невозможно получить доступ к явной реализации элемента интерфейса с помощью его квалифицированного имени члена интерфейса в вызове метода, доступе к свойствам, доступе к событиям или доступе индексатора. Явная реализация члена интерфейса может быть доступна только через экземпляр интерфейса и в этом случае ссылается просто по имени члена.

Это ошибка во время компиляции для явной реализации члена интерфейса, чтобы включить любые модификаторы (§15.6), отличные от extern или async.

Это ошибка во время компиляции для явной реализации метода интерфейса для включения type_parameter_constraints_clauses. Ограничения для реализации универсального явного метода интерфейса наследуются от метода интерфейса.

Примечание. Явные реализации элементов интерфейса имеют разные характеристики специальных возможностей, чем другие члены. Так как явные реализации элементов интерфейса никогда не доступны через полное имя члена интерфейса в вызове метода или доступе к свойству, они находятся в смысле закрытыми. Тем не менее, так как они могут быть доступны через интерфейс, они имеют смысл также как общедоступный, как интерфейс, в котором они объявлены. Явные реализации элементов интерфейса служат двумя основными целями:

  • Так как явные реализации элементов интерфейса недоступны через экземпляры классов или структур, они позволяют исключить реализации интерфейса из общедоступного интерфейса класса или структуры. Это особенно полезно, если класс или структура реализует внутренний интерфейс, который не является интересом для потребителя этого класса или структуры.
  • Явные реализации элементов интерфейса позволяют дискредитировать членов интерфейса с той же сигнатурой. Без явных реализаций элементов интерфейса было бы невозможно для класса или структуры иметь различные реализации элементов интерфейса с одной сигнатурой и типом возвращаемого типа, так как было бы невозможно для класса или структуры иметь любую реализацию во всех членах интерфейса с одной и той же подписью, но с различными типами возвращаемых данных.

конечная заметка

Чтобы явная реализация члена интерфейса была допустимой, класс или структура должны назвать интерфейс в списке базовых классов, содержащий член, имя члена которого, тип, число параметров типа и типы параметров точно соответствуют типу реализации явного элемента интерфейса. Если член функции интерфейса имеет массив параметров, соответствующий параметр связанной реализации явного интерфейса разрешен, но не требуется, чтобы params модификатор. Если элемент функции интерфейса не имеет массив параметров, то связанная явная реализация элемента интерфейса не должна иметь массив параметров.

Пример. Таким образом, в следующем классе

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

Объявление IComparable.CompareTo результатов в ошибке во время компиляции, так как IComparable не указано в списке Shape базовых классов и не является базовым интерфейсом ICloneable. Аналогичным образом, в объявлениях

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

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

Объявление ICloneable.Clone приводит Ellipse к ошибке во время компиляции, так как ICloneable не указано явно в списке базовых Ellipseклассов.

пример конца

Полное имя члена интерфейса явной реализации элемента интерфейса должно ссылаться на интерфейс, в котором был объявлен член.

Пример. Таким образом, в объявлениях

interface IControl
{
    void Paint();
}

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

class TextBox : ITextBox
{
    void IControl.Paint() {...}
    void ITextBox.SetText(string text) {...}
}

Явная реализация элемента интерфейса Paint должна быть записана как IControl.Paint, а не ITextBox.Paint.

пример конца

18.6.3 Уникальность реализованных интерфейсов

Интерфейсы, реализованные объявлением универсального типа, должны оставаться уникальными для всех возможных созданных типов. Без этого правила невозможно определить правильный метод для вызова определенных созданных типов.

Пример. Предположим, что объявление универсального класса было разрешено писать следующим образом:

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() {...}
}

Если это разрешено, невозможно определить, какой код будет выполняться в следующем случае:

I<int> x = new X<int, int>();
x.F();

пример конца

Чтобы определить, является ли список интерфейсов допустимым объявлением универсального типа, выполняются следующие действия.

  • Давайте рассмотрим L список интерфейсов, непосредственно указанных в универсальном классе, структуре или объявлении Cинтерфейса.
  • Добавьте все L базовые интерфейсы интерфейсов уже в L.
  • Удалите все дубликаты из L.
  • Если любой возможный созданный тип, созданный из C этого типа, после замены Lаргументов типа на , вызовите два интерфейса L идентичным, то объявление C недопустимо. Объявления ограничений не учитываются при определении всех возможных созданных типов.

Примечание. В приведенном выше объявлении X класса список L интерфейсов состоит из l<U> и I<V>. Объявление недопустимо, так как любой созданный тип с UV одинаковым типом приведет к тому, что эти два интерфейса будут идентичными типами. конечная заметка

Интерфейсы, указанные на разных уровнях наследования, можно объединить:

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() {...}
}

Этот код действителен, даже если Derived<U,V> реализует оба I<U> и I<V>. Код

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

вызывает метод в Derived, так как Derived<int,int>' эффективно повторно реализует I<int> (§18.6.7).

18.6.4 Реализация универсальных методов

Если универсальный метод неявно реализует метод интерфейса, ограничения, заданные для каждого параметра типа метода, должны быть эквивалентны в обоих объявлениях (после замены любых параметров типа интерфейса соответствующими аргументами типа), где параметры типа метода определяются порядковых позиций слева направо.

Пример: в следующем коде:

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
}

метод C.F<T> неявно I<object,C,string>.F<T>реализует. В этом случае не требуется (ни разрешено) указывать ограничениеC.F<T>, T: object так как object это неявное ограничение для всех параметров типа. Метод C.G<T> неявно реализуется I<object,C,string>.G<T> , так как ограничения соответствуют ограничениям в интерфейсе после замены параметров типа интерфейса соответствующими аргументами типа. Ограничение метода C.H<T> является ошибкой, так как в этом случае не могут использоваться в качестве ограничений запечатанные типы (string в данном случае). Опущение ограничения также будет ошибкой, так как ограничения реализации неявного метода интерфейса необходимы для сопоставления. Таким образом, неявно реализовать I<object,C,string>.H<T>невозможно. Этот метод интерфейса можно реализовать только с помощью явной реализации члена интерфейса:

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);
    }
}

В этом случае явная реализация элемента интерфейса вызывает открытый метод с строго слабыми ограничениями. Назначение от t к s допустимо, так как T наследует ограничение T: string, даже если это ограничение не является явным в исходном коде. пример конца

Примечание. Если универсальный метод явно реализует метод интерфейса, ограничения не допускаются для метода реализации (§15.7.1, §18.6.2). конечная заметка

Сопоставление интерфейсов 18.6.5

Класс или структура должны предоставлять реализации всех элементов интерфейсов, перечисленных в списке базовых классов класса или структуры. Процесс поиска реализаций элементов интерфейса в реализации класса или структуры называется сопоставлением интерфейсов.

Сопоставление интерфейсов для класса или структуры C находит реализацию для каждого члена каждого интерфейса, указанного в списке базовых Cклассов. Реализация определенного элемента интерфейса, где I.M находится интерфейс, в котором объявлен членIM, определяется путем изучения каждого класса или структурыS, начиная с C и повторяющегося для каждого последовательного Cбазового класса, пока не будет найдено совпадение:

  • Если S содержит объявление явной реализации элемента интерфейса, соответствующего I , и Mэтот элемент является реализацией I.M.
  • В противном случае, если S содержит объявление нестатитического общедоступного члена, соответствующего M, этот элемент является реализацией I.M. Если несколько элементов совпадают, это не указано, какой член является реализацией I.M. Эта ситуация может возникать только в том случае, если S это созданный тип, в котором два члена, объявленные в универсальном типе, имеют разные подписи, но аргументы типа делают их подписи идентичными.

Ошибка во время компиляции возникает, если реализации не могут находиться для всех элементов всех интерфейсов, указанных в списке Cбазовых классов. Члены интерфейса включают те элементы, которые наследуются от базовых интерфейсов.

Члены созданного типа интерфейса считаются параметрами типа, замененными соответствующими аргументами типа, как указано в §15.3.3.

Пример. Например, учитывая объявление универсального интерфейса:

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

Созданный интерфейс I<string[]> содержит элементы:

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

пример конца

В целях сопоставления интерфейсов класс или член A структуры соответствует члену B интерфейса, когда:

  • A и B являются методами, а также именами, типами и списками A параметров и B идентичными.
  • Aи B являются свойствами, именем и типом A и B идентичными и имеют те же методы доступа, что A и B (Aразрешено иметь дополнительные методы доступа, если он не является явной реализацией элемента интерфейса).
  • A и B являются событиями, а также именами и типами A и B идентичными.
  • Aи B являются индексаторами, списки AB типов и параметров идентичны и имеют те же методы доступа, что A и B (Aразрешено иметь дополнительные методы доступа, если он не является явной реализацией элемента интерфейса).

Важные последствия алгоритма сопоставления интерфейсов:

  • Явные реализации элементов интерфейса имеют приоритет над другими членами того же класса или структуры при определении класса или элемента структуры, реализующего член интерфейса.
  • Ни открытые, ни статические члены не участвуют в сопоставлении интерфейсов.

Пример. В следующем коде

interface ICloneable
{
    object Clone();
}

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

ICloneable.Clone Член C становится реализацией Clone в ICloneable, так как явные реализации элементов интерфейса имеют приоритет над другими членами.

пример конца

Если класс или структура реализует два или более интерфейсов, содержащих член с одинаковым именем, типом и типами параметров, можно сопоставить каждый из этих элементов интерфейса с одним классом или элементом структуры.

Пример:

interface IControl
{
    void Paint();
}

interface IForm
{
    void Paint();
}

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

Paint Здесь методы обоих IControl и IForm сопоставляются с методом PaintPage. Конечно, можно также иметь отдельные явные реализации элементов интерфейса для двух методов.

пример конца

Если класс или структура реализует интерфейс, содержащий скрытые элементы, некоторые элементы могут быть реализованы с помощью явных реализаций элементов интерфейса.

Пример:

interface IBase
{
    int P { get; }
}

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

Реализация этого интерфейса потребует по крайней мере одной явной реализации члена интерфейса и будет принимать одну из следующих форм.

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() {...}
}

пример конца

Если класс реализует несколько интерфейсов, имеющих один базовый интерфейс, может быть только одна реализация базового интерфейса.

Пример. В следующем коде

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) {...}
}

Невозможно иметь отдельные реализации для именованного IControl в списке базовых классов, IControl наследуемого и ITextBox унаследованногоIControlIListBox. Действительно, для этих интерфейсов нет понятия отдельного удостоверения. Скорее, реализации ITextBoxи совместно используют одну и IListBox ту же реализацию IControlи ComboBox просто считаются реализацией трех интерфейсов, IControlи ITextBox.IListBox

пример конца

Члены базового класса участвуют в сопоставлении интерфейсов.

Пример. В следующем коде

interface Interface1
{
    void F();
}

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

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

Метод F , используемый в Class1Class2's реализации Interface1.

пример конца

Наследование реализации интерфейса 18.6.6

Класс наследует все реализации интерфейса, предоставляемые его базовыми классами.

Без явной повторной реализации интерфейса производный класс не может изменять сопоставления интерфейсов, наследуемых от его базовых классов.

Пример. В объявлениях

interface IControl
{
    void Paint();
}

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

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

Paint Метод в TextBox скрытии Paint методаControl, но он не изменяет сопоставление Control.Paint на IControl.Paint, а вызовы Paint к экземплярам классов и экземплярам интерфейса будут иметь следующие эффекты.

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

пример конца

Однако при сопоставлении метода интерфейса с виртуальным методом в классе можно переопределить виртуальный метод и изменить реализацию интерфейса.

Пример. Перезапись объявлений выше в

interface IControl
{
    void Paint();
}

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

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

Теперь будут наблюдаться следующие эффекты.

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

пример конца

Так как явные реализации элементов интерфейса не могут быть объявлены виртуальными, невозможно переопределить явную реализацию члена интерфейса. Тем не менее, он является совершенно допустимым для явной реализации члена интерфейса для вызова другого метода, и что другой метод может быть объявлен виртуальным, чтобы разрешить производным классам переопределить его.

Пример:

interface IControl
{
    void Paint();
}

class Control : IControl
{
    void IControl.Paint() { PaintControl(); }
    protected virtual void PaintControl() {...}
}

class TextBox : Control
{
    protected override void PaintControl() {...}
}

Здесь классы, производные от Control этого, могут специализировать реализацию IControl.Paint путем переопределения PaintControl метода.

пример конца

Повторное внедрение интерфейса 18.6.7

Класс, наследующий реализацию интерфейса, может повторно реализовать интерфейс, включив его в список базовых классов.

Повторная реализация интерфейса соответствует точно тем же правилам сопоставления интерфейсов, что и начальная реализация интерфейса. Таким образом, сопоставление унаследованных интерфейсов не влияет ни на какое из сопоставлений интерфейсов, установленных для повторной реализации интерфейса.

Пример. В объявлениях

interface IControl
{
    void Paint();
}

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

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

Тот факт, что Control сопоставляется IControl.PaintControl.IControl.Paint с ней, не влияет на повторную реализациюMyControl, на которую сопоставляется IControl.PaintMyControl.Paint.

пример конца

Унаследованные объявления открытых членов и наследуемые явные объявления элементов интерфейса участвуют в процессе сопоставления интерфейсов для повторно реализованных интерфейсов.

Пример:

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() {}
}

Здесь реализация в сопоставлении IMethods методов интерфейса с Derived, Derived.FBase.IMethods.Gа также Derived.IMethods.H.Base.I

пример конца

Когда класс реализует интерфейс, он неявно реализует все базовые интерфейсы интерфейса. Аналогичным образом, повторная реализация интерфейса также неявно является повторной реализацией всех базовых интерфейсов интерфейса.

Пример:

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() {...}
}

Здесь повторная реализация IDerived также повторно реализует IBase, сопоставляется IBase.F с D.F.

пример конца

18.6.8 Абстрактные классы и интерфейсы

Как и не абстрактный класс, абстрактный класс должен предоставлять реализации всех элементов интерфейсов, перечисленных в списке базовых классов класса. Однако абстрактный класс позволяет сопоставить методы интерфейса с абстрактными методами.

Пример:

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

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

Здесь реализация карт IMethods и абстрактных F методов, которые должны быть переопределены в не абстрактных классах, производных от GC .

пример конца

Явные реализации элементов интерфейса не могут быть абстрактными, но явные реализации элементов интерфейса, конечно, разрешены вызывать абстрактные методы.

Пример:

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();
}

Здесь не абстрактные классы, производные от C них, должны быть переопределяются FF и GG, таким образом, предоставляя фактическую реализацию IMethods.

пример конца