Уникальность реализованных интерфейсов
Интерфейсы, реализованные в объявлении уникального типа, должны оставаться уникальными для всех сформированных типов. Если бы это правило не действовало, было бы невозможно определить правильный метод для вызова в конкретных сформированных типах. Например, предположим, что объявление универсального класса может иметь следующий вид:
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 состоит из интерфейсов I<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> (§13.4.6).
Реализация универсальных методов
Если в универсальном методе неявно реализуется метод интерфейса, то ограничения, заданные для каждого из параметров типа метода, должны быть одинаковы в обоих объявлениях (после замены любых параметров типа интерфейса соответствующими аргументами типа), где параметры типа метода указываются их положением слева направо.
Тем не менее, если в универсальном методе явно реализуется метод интерфейса, ограничения для реализующего метода не допускаются. Эти ограничения наследуются из метода интерфейса
interface I<A,B,C>
{
void F<T>(T t) where T: A;
void G<T>(T t) where T: B;
void H<T>(T t) where T: C;
}
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, хотя это ограничение и не указано в исходном коде.
Сопоставление интерфейсов
В классе или структуре должны содержаться реализации всех членов интерфейсов, включенных в список базовых классов такого класса или структуры. Процедура выявления реализаций членов интерфейса в реализующих их классе или структуре называется сопоставлением интерфейсов.
При сопоставлении интерфейсов для класса или структуры C обнаруживает реализацию каждого из членов интерфейса, указанных в списке базовых классов C. Реализация конкретного члена интерфейса I.M, где I указывает интерфейс, в котором объявлен член M, определяется путем проверки каждого класса или структуры S, начиная с C и последовательно проходя по каждому базовому классу C до выявления совпадения:
· Если S содержит объявление явной реализации члена интерфейса, которая соответствует I и M, этот член является реализацией члена I.M.
· В обратном случае, если S содержит объявление открытого члена, не являющегося статическим, который соответствует M, то реализацией члена I.M является этот член. При выявлении совпадения для нескольких членов не указывается, какой из них является реализацией члена I.M. Эта ситуация может произойти только в том случае, если S относится к сформированному типу, в котором два члена, согласно объявлению в универсальном типе, имеют разные подписи, однако аргументы типа делают их подписи идентичными.
Если не удается выявить реализации для всех членов всех интерфейсов, указанных в списке базовых классов C, возникает ошибка при компилировании. Обратите внимание, что члены интерфейса содержат члены, наследуемые из базовых интерфейсов.
При сопоставлении интерфейсов член класса 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() {...}
}
реализацией метода Clone интерфейса ICloneable становится член ICloneable.Clone класса C, поскольку явные реализации членов интерфейса имеют приоритет по отношению к другим членам.
Если в классе или структуре реализуется два или более интерфейса, содержащих член с одинаковым именем, типом и типами параметров, можно сопоставить каждый из таких членов интерфейса единственному члену класса или структуры. Пример
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 C: IDerived
{
int IBase.P { get {...} }
int IDerived.P() {...}
}
class C: IDerived
{
public int P { get {...} }
int IDerived.P() {...}
}
class C: 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
{
new public void G() {}
}
метод F класса Class1 используется в реализации интерфейса Interface1 в классе Class2.