Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
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_modifier (§18.2.2), а затем необязательный частичный модификатор (§15.2.7), за которым следует ключевое слово и идентификатор, который называет интерфейс, за которым следует необязательная спецификация variant_type_parameter_list (§18.2.3), а затем необязательная спецификация interface_base (§18.2.4), за которой следует необязательная спецификация interface
(§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
, protected
, internal
и private
контролируют доступность интерфейса. В зависимости от контекста, в котором происходит объявление интерфейса, может быть разрешено только некоторые из этих модификаторов (§7.5.2). Если объявление частичного типа (§15.2.7) включает спецификацию доступности, с помощью модификаторов , public
, protected
и internal
, применяются правила в §15.2.2.
Списки параметров типа вариант 18.2.3
18.2.3.1 Общие
Списки параметров типа Variant могут существовать только в типах интерфейсов и делегатов. Различие от обычных type_parameter_list заключается в наличии необязательной variance_annotation для каждого параметра типа.
variant_type_parameter_list
: '<' variant_type_parameter (',' variant_type_parameter)* '>'
;
variant_type_parameter
: 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
является параметром контравариантного типа -
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
,ITextBox
иIListBox
. Другими словами, приведенный выше интерфейс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, и должен быть безопасным для ввода, если есть аксессор set.
События интерфейса 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, и безопасным для ввода, если есть метод доступа set.
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 { 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 } }
Первая инструкция вызывает ошибку компиляции, так как поиск члена (§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
.Интуитивно понятное правило скрытия в интерфейсах с множественным наследованием следующее: если член скрыт в любом из путей доступа, он скрыт во всех путях доступа. Поскольку путь доступа от
IDerived
доILeft
доIBase
скрываетIBase.F
, этот член также скрыт на пути доступа отIDerived
доIRight
доIBase
.заключительный пример
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_clause. Ограничения для реализации универсального явного метода интерфейса наследуются от метода интерфейса.
Примечание. Явные реализации членов интерфейса имеют другие характеристики доступности по сравнению с другими членами. Так как явные реализации элементов интерфейса никогда не доступны через полное имя члена интерфейса в вызове метода или доступе к свойству, они по сути являются приватными. Тем не менее, поскольку они могут быть доступны через интерфейс, они по сути так же публичны, как и сам интерфейс, в котором они объявлены. Явные реализации элементов интерфейса служат двумя основными целями:
- Так как явные реализации элементов интерфейса недоступны через экземпляры классов или структур, они позволяют исключить реализации интерфейса из общедоступного интерфейса класса или структуры. Это особенно полезно, если класс или структура реализует внутренний интерфейс, который не является интересом для потребителя этого класса или структуры.
- Явные реализации элементов интерфейса позволяют дискредитировать членов интерфейса с той же сигнатурой. Без явных реализаций элементов интерфейса класс или структура не могли бы иметь различные реализации элементов интерфейса с одной сигнатурой и типом возвращаемого значения, так же как и не могли бы иметь какую-либо реализацию членов интерфейса с одинаковой сигнатурой, но с разными типами возвращаемых значений.
конечная заметка
Чтобы реализация явного элемента интерфейса была допустимой, класс или структура должны в списке базовых классов указать интерфейс, содержащий элемент, имя, тип, количество параметров типа и типы параметров которого точно соответствуют соответствующим характеристикам реализации явного элемента интерфейса. Если член интерфейса имеет массив параметров, то соответствующий параметр реализации связанного явного интерфейса может, но не обязан, иметь модификатор 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>
. Объявление недопустимо, так как любой созданный тип сU
V
одинаковым типом приведет к тому, что эти два интерфейса будут идентичными типами. конечная заметка
Интерфейсы, указанные на разных уровнях наследования, можно объединить:
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
это неявное ограничение для всех параметров типа. Метод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
, где I
— это интерфейс, в котором объявлен член M
, определяется путем изучения каждого класса или структуры 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
являются индексаторами, спискиA
B
типов и параметров идентичны и имеют те же методы доступа, что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
сопоставляются с методомPaint
Page
. Конечно, можно также иметь отдельные явные реализации элементов интерфейса для двух методов.заключительный пример
Если класс или структура реализует интерфейс, содержащий скрытые элементы, некоторые элементы могут быть реализованы с помощью явных реализаций элементов интерфейса.
Пример:
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
, иIControl
, унаследованногоIListBox
. Действительно, для этих интерфейсов нет понятия отдельной идентичности. Скорее, реализации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
используется вClass1
для реализацииClass2'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.Paint
наControl.IControl.Paint
, не влияет на повторную реализацию вMyControl
, которая отображаетIControl.Paint
наMyControl.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.F
,Base.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
иG
на абстрактные методы, которые должны быть переопределены в неабстрактных классах, производных отC
.заключительный пример
Явные реализации элементов интерфейса не могут быть абстрактными, но явные реализации элементов интерфейса, конечно, разрешены вызывать абстрактные методы.
Пример:
interface IMethods { void F(); void G(); } abstract class C: IMethods { void IMethods.F() { FF(); } void IMethods.G() { GG(); } protected abstract void FF(); protected abstract void GG(); }
Здесь неабстрактные классы, производные от
C
, должны переопределитьFF
иGG
, таким образом предоставляя фактическую реализациюIMethods
.заключительный пример
ECMA C# draft specification