Массивы и универсальный интерфейс IList

В одномерном массиве T[] реализуется интерфейс System.Collections.Generic.IList<T> (сокращенно IList<T>) и его базовые интерфейсы. В связи с этим выполняется неявное преобразование из T[] в IList<T> и его базовые интерфейсы. В дополнение к этому при наличии неявного преобразования ссылок из S в T в массиве S[] реализуется интерфейс IList<T> и выполняется неявное преобразование ссылок из S[] в IList<T> и его базовые интерфейсы (§6.1.6). При наличии явного преобразования ссылок из S в T выполняется явное преобразование ссылок из S[] в IList<T> и его базовые интерфейсы (§6.2.4). Пример:

using System.Collections.Generic;

class Test
{
static void Main() {
string[] sa = new string[5];
object[] oa1 = new object[5];
object[] oa2 = sa;

IList<string> lst1 = sa; // Ok
IList<string> lst2 = oa1; // Error, cast needed
IList<object> lst3 = sa; // Ok
IList<object> lst4 = oa1; // Ok

IList<string> lst5 = (IList<string>)oa1; // Exception
IList<string> lst6 = (IList<string>)oa2; // Ok
}
}

Присваивание lst2 = oa1 приведет к ошибке компилирования, поскольку преобразование из object[] в IList<string> должно быть явным и не может выполняться неявно. Приведение типов в строке (IList<string>)oa1 приведет к созданию исключения во время выполнения, так как переменная oa1 ссылается на объект object[], а не на объект string[]. Однако приведение типов в строке (IList<string>)oa2 приведет к созданию исключения во время выполнения, так как переменная oa2 ссылается на объект string[].

При выполнении явного или неявного преобразования ссылок из S[] в IList<T> также выполняется явное преобразование ссылок из интерфейса IList<T> и его базовых интерфейсов в S[] (§6.2.4).

Если в типе массива S[] реализуется интерфейс IList<T>, некоторые из членов реализованного интерфейса могут создавать исключения. Описание точного поведения этой реализации интерфейса выходит за рамки настоящей спецификации.

Создание массива

Экземпляры массива создаются при помощи выражений_создания_массива (§7.6.10.4) либо путем объявлений полей или локальных переменных, содержащих инициализатор_массива (§12.6).

Ранг массива и длина каждого из его измерений устанавливаются при создании экземпляра массива и остаются неизменными в течение всего времени жизни этого экземпляра. Другими словами, нельзя изменить ранг массива или длину его измерений.

Экземпляр массива всегда имеет тип массива. Тип System.Array является абстрактным типом, создание экземпляров которого невозможно.

Элементы массива, созданного с использованием выражений_создания_массива, всегда инициализируются значениями по умолчанию (§5.2).

Доступ к элементам массива

Доступ к элементам массива осуществляется при помощи выражений доступа к элементам (§7.6.6.1) в форме A[I1, I2, ..., IN], где A является выражением типа массива, а каждый элемент IX является выражением типа int, uint, long, ulong либо может быть неявно преобразован в один или несколько из этих типов. Результатом осуществления доступа к элементу массива является переменная, а именно элемент массива, выбранный по индексу.

Элементы массива могут перечисляться с использованием оператора foreach (§8.8.4).

Члены массива

Каждый тип массива наследуют члены, объявленные типом System.Array.

Ковариация массивов

Если для любых двух переменных A и B ссылочного типа выполняется неявное (§6.1.6) или явное преобразование ссылок (§6.2.4) из A в B, такое же преобразование ссылок доступно из массива A[R] в массив B[R], где R указывает спецификацию_ранга (одинаковую для обоих типов массивов). Эта связь называется ковариацией массивов. Ковариация массивов в частности означает, что значение с типом массива A[R] фактически может быть ссылкой на экземпляр типа массива B[R] при условии, что доступно неявное преобразование ссылок из B в A.

В связи с существованием ковариации массива при присваивании элементам массива ссылочного типа выполняется проверка времени выполнения, которая гарантирует, что присваиваемое элементу массива значение имеет допустимый тип (§7.17.1). Пример:

class Test
{
static void Fill(object[] array, int index, int count, object value) {
for (int i = index; i < index + count; i++) array[i] = value;
}

static void Main() {
string[] strings = new string[100];
Fill(strings, 0, 100, "Undefined");
Fill(strings, 0, 10, null);
Fill(strings, 90, 10, 0);
}
}

Присвоение массиву array[i] в методе Fill включает неявную проверку времени выполнения, гарантирующую, что объект, на который ссылается аргумент value, имеет значение null или является экземпляром типа, совместимого с фактическим типом элементов array. В методе Main первые два вызова метода Fill выполняются успешно, но третий вызов приводит к созданию исключения System.ArrayTypeMismatchException после выполнения первого присвоения массиву array[i]. Исключение возникает в связи с тем, что упакованное значение int не может храниться в массиве с типом string.

Ковариация массивов не расширяется до массивов типов значений. Например, не существует преобразования, разрешающего обрабатывать int[] как object[].

Инициализаторы массива

Инициализаторы массива указываются в объявлениях полей (§10.5), объявлениях локальных переменных (§8.5.1) и выражениях создания массива (§7.6.10.4):

array-initializer:
{ variable-initializer-listopt }
{ variable-initializer-list , }

variable-initializer-list:
variable-initializer
variable-initializer-list , variable-initializer

variable-initializer:
expression
array-initializer

Инициализатор массива состоит из последовательности переменных инициализаторов, заключенных в фигурные скобки «{» и «}» и разделенных запятыми «,». Каждый инициализатор переменной представляет собой выражение или (для многомерных массивов) инициализатор вложенного массива.

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

int[] a = {0, 2, 4, 6, 8};

он просто представляет собой краткую запись соответствующего выражения создания массива

int[] a = new int[] {0, 2, 4, 6, 8};

Инициализатор одномерного массива должен состоять из последовательности выражений, позволяющих выполнять присваивание для типа элементов массива. Эти выражения инициализируют элементы массива в порядке по возрастанию, начиная с элемента с нулевым индексом. Число выражений в инициализаторе массива определяет длину создаваемого экземпляра массива. Например, приведенный выше инициализатор массива создает экземпляр массива int[] с длиной 5, а затем инициализирует этот экземпляр следующими значениями:

a[0] = 0; a[1] = 2; a[2] = 4; a[3] = 6; a[4] = 8;

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

int[,] b = {{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}};

создает двумерный массив с длиной пять для левого измерения и с длиной два для правого измерения

int[,] b = new int[5, 2];

а затем инициализирует экземпляр массива следующими значениями:

b[0, 0] = 0; b[0, 1] = 1;
b[1, 0] = 2; b[1, 1] = 3;
b[2, 0] = 4; b[2, 1] = 5;
b[3, 0] = 6; b[3, 1] = 7;
b[4, 0] = 8; b[4, 1] = 9;

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

int[,] c = {};

создает двухмерный массив с нулевой длиной для самого правого и самого левого измерения:

int[,] c = new int[0, 0];

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

int i = 3;
int[] x = new int[3] {0, 1, 2}; // OK
int[] y = new int[i] {0, 1, 2}; // Error, i not a constant
int[] z = new int[3] {0, 1, 2, 3}; // Error, length/initializer mismatch

В этом примере инициализатор для переменной y приведет к возникновению ошибки компилирования, поскольку выражение длины массива не является константой. Инициализатор для переменной z также приведет к возникновению ошибки компилирования, поскольку заданная длина и число элементов в инициализаторе не совпадают.

Интерфейсы

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

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

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

Объявление_интерфейса является объявлением_типа (§9.6), где объявляется новый тип интерфейса.

interface-declaration:
attributesopt interface-modifiersopt partialopt interface
identifier variant-type-parameter-listopt interface-baseopt
type-parameter-constraints-clausesopt interface-body ;opt

Объявление_интерфейса состоит из необязательного набора атрибутов (§17), за которым следуют необязательный набор модификаторов_интерфейса (§13.1.1), необязательный модификатор partial, ключевое слово interface и идентификатор, именующий интерфейс, необязательная спецификация списка_параметров_типа_варианта (§13.1.3), необязательная спецификация базы_интерфейса (§13.1.4), необязательная спецификация предложений_ограничений_параметров_типов (§10.1.5) и тело_интерфейса (§13.1.5), которое может завершаться точкой с запятой.

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

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

interface-modifiers:
interface-modifier
interface-modifiers interface-modifier

interface-modifier:
new
public
protected
internal
private

Включение одного модификатора в объявление интерфейса несколько раз приведет к возникновению ошибки при компилировании.

Модификатор new разрешен только в том случае, если интерфейс определяется внутри класса. Он указывает, что в интерфейсе скрыт унаследованный член с таким же именем (см. §10.3.4).

Модификаторы public, protected, internal и private управляют доступом к интерфейсу. Допустимые модификаторы определяются контекстом объявления интерфейса (§3.5.1).

Модификатор partial

Модификатор partial указывает, что данное объявление_интерфейса является частичным объявлением типа. Несколько частичных объявлений интерфейса с одним именем в едином пространстве имен или объявлении типа объединяются в одно объявление интерфейса с соблюдением правил, приведенных в §10.2.

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

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

variant-type-parameter-list:
< variant-type-parameters >

variant-type-parameters:
attributesopt variance-annotationopt type-parameter
variant-type-parameters , attributesopt variance-annotationopt 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 – инвариантным.

Безопасность вариативности

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

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

· T является контравариантным типом параметра;

· T — это тип массива с типом элементов, небезопасным при выводе.

· T является типом интерфейса или делегата S<A1,… AK>, сформированным из универсального типа S<X1, .. XK>, в котором по меньшей мере один Ai содержит один из следующих вариантов:

o Xi является ковариантным или инвариантным, а Ai является небезопасным при выводе.

o Xi является ковариантным или инвариантным, а Ai является безопасным при выводе.

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

· T является параметром ковариантного типа.

· T является типом массива с типом элементов, небезопасным при вводе.

· T является типом интерфейса или делегата S<A1,… AK>, сформированным из универсального типа S<X1, .. XK>, в котором по меньшей мере один Ai содержит один из следующих вариантов:

o Xi является ковариантным или инвариантным, а Ai является небезопасным при вводе.

o Xi является контравариантным или инвариантным, а Ai является безопасным при выводе.

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

Тип является безопасным при выводе, если он не является небезопасным при выводе, а тип, безопасный при вводе, — это тип, который не является небезопасным при вводе.

13.1.3.2 Вариантные преобразования

Назначение аннотаций вариативности – предоставить более мягкие преобразования (но безопасные для типа) типам интерфейса и делегатов. Определения неявных (§6.1) и явных (§6.2) преобразований используют указание возможности преобразования вариативности, которое определяется следующим образом:

Тип T<A1, …, An> является вариативно-преобразуемым в тип T<B1, …, Bn>, если T является типом интерфейса или делегата, объявленного с параметрами типа варианта T<X1, …, Xn>, и для каждого параметра типа варианта Xi имеет смысл один из следующих пунктов.

· Xi является ковариантным, и существует неявное преобразование идентификации или ссылки из Ai в Bi.

· Xi является контравариантным, и существует неявное преобразование идентификации или ссылки из Bi в Ai.

· Xi является инвариантным и существует преобразование идентификации из Ai в Bi.

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

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

interface-base:
: interface-type-list

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

Уровень доступа явных базовых интерфейсов для конкретного интерфейса должен быть не ниже уровня доступа самого интерфейса (§3.5.4). Например, указание интерфейса с уровнем доступа private или internal в качестве базы интерфейса для интерфейса с уровнем доступа public приведет к ошибке времени компиляции.

Явное или неявное наследование интерфейса из самого себя также приведет к возникновению ошибки компилирования.

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

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.

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

Тело интерфейса

В теле интерфейса определяются члены этого интерфейса.

interface-body:
{ interface-member-declarationsopt }

Члены интерфейса

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

interface-member-declarations:
interface-member-declaration
interface-member-declarations interface-member-declaration

interface-member-declaration:
interface-method-declaration
interface-property-declaration
interface-event-declaration
interface-indexer-declaration

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

Члены интерфейса неявно имеют уровень доступа «public». Включение модификаторов в объявления членов интерфейса приведет к возникновению ошибки компилирования. В частности, члены интерфейсов не могут объявляться с модификаторами abstract, public, protected, internal, private, virtual, override и static.

Пример:

public delegate void StringListEvent(IStringList sender);

public interface IStringList
{
void Add(string s);

int Count { get; }

event StringListEvent Changed;

string this[int index] { get; set; }
}

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

Объявление интерфейса создает новую область объявления (§3.3), а объявления членов интерфейса, содержащиеся непосредственно в объявлении интерфейса, вводят в эту область объявления новых членов. К объявлениям членов интерфейса применяются следующие правила:

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

· Имя свойства или события должно отличаться от имен всех остальных членов, объявленных в том же интерфейсе.

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

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

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

Обратите внимание, что члены в классе object не являются, строго говоря, членами какого-либо интерфейса (§13.2). Тем не менее, члены в классе object доступны при поиске членов интерфейса любого типа (§7.4).

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

Методы интерфейса объявляются с использованием объявлений методов интерфейса:

interface-method-declaration:
attributesopt newopt return-type identifier type-parameter-list
( formal-parameter-listopt ) type-parameter-constraints-clausesopt ;

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

Каждый формальный тип параметра метода интерфейса должен быть безопасным при вводе (§13.1.3.1), а тип возвращаемого значения – void или безопасный при выводе. Более того, каждое ограничение типа класса, типа интерфейса и типа параметра на любой тип параметра метода должно быть безопасным при вводе.

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

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

является недействительным, поскольку использование 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, таким образом безопасность типа будет под угрозой.

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

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

interface-property-declaration:
attributesopt newopt type identifier { interface-accessors }

interface-accessors:
attributesopt get ;
attributesopt set ;
attributesopt get ; attributesopt set ;
attributesopt set ; attributesopt get ;

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

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

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

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

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

interface-event-declaration:
attributesopt newopt event type identifier ;

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

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

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

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

interface-indexer-declaration:
attributesopt newopt type this [ formal-parameter-list ] { interface-accessors }

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

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

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

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

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

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

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

В этом примере

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

выполнение первых двух операторов ведет к ошибкам при компилировании, поскольку поиск членов (§7.4) метода 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 в результате применения правил разрешения перегрузки, рассматриваемых в разделе §7.5.3. Аналогичным образом, при вызове метода 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.

Интуитивное правило скрытия членов в интерфейсах с множественным наследованием звучит так: если член скрыт в одном из путей доступа, он скрыт и во всех остальных путях доступа. Поскольку член IBase.F скрыт в пути доступа от IDerived к ILeft и к IBase, этот член также скрыт в пути доступа от IDerived к IRight и к IBase.

Наши рекомендации