Интерфейс может не иметь или иметь сколько угодно интерфейсов-предков, в последнем случае он наследует все элементы всех своих базовых интерфейсов, начиная с самого верхнего уровня. Базовые интерфейсы должны быть доступны в не меньшей степени, чем их потомки. Например, нельзя использовать интерфейс, описанный со спецификатором private или internal, в качестве базового для открытого (public) интерфейса1.
Как и в обычной иерархии классов, базовые интерфейсы определяют общее поведение, а их потомки конкретизируют и дополняют его. В интерфейсе-потомке можно также указать элементы, переопределяющие унаследованные элементы с такой же сигнатурой. В этом случае перед элементом указывается ключевое слово new, как и в аналогичной ситуации в классах. С помощью этого слова соответствующий элемент базового интерфейса скрывается. Вот пример из документации С#:
interface IBase
{
void F(int i);
}
interface ILeft: IBase
{
new void F(int i); // переопределение метода F
}
interface Iright: IBase
{
void GO;
}
interface Iderived: ILeft, IRight { }
class A
{
void Test(IDerived d)
{
d.F(1); // Вызывается ILeft.F
((IBase)d).F(1); // Вызывается IBase.F
((ILeft)d).F(1); // Вызывается ILeft.F
((IRight)d).F(1); // Вызывается IBase.F
}
}
Метод F из интерфейса IBase скрыт интерфейсом ILeft, несмотря на то что в цепочке IDerived — IRight — IBase он не переопределялся.
Класс, реализующий интерфейс, должен определять все его элементы, в том числе унаследованные. Если при этом явно указывается имя интерфейса, оно
Естественно, интерфейс не может быть наследником самого себя
должно ссылаться на тот интерфейс, в котором был описан соответствующий элемент, например:
class A: IRight
{
IRight.G() {... }
IBase.F(int i){...} // IRight.F(int i) - нельзя
}
Интерфейс, на собственные или унаследованные элементы которого имеется явная ссылка, должен быть указан в списке предков класса, например:
Class В: А
{
// IRight.GO {... } нельзя!
}
class С: A, IRight
{
IRight.G() {... } // можно
IBase.F(int i){...} // можно
}
Класс наследует все методы своего предка, в том числе те, которые реализовывали интерфейсы. Он может переопределить эти методы с помощью спецификатора new, но обращаться к ним можно будет только через объект класса. Если использовать для обращения ссылку на интерфейс, вызывается не переопределенная версия:
interface IBase
{
void А();
}
class Base: IBase
{
public void А() {... }
}
class Derived: Base
{
new public void А() {... }
}
//...
Derived d = new Derived ();
d.A(); // вызывается Derived.А();
IBase id = d;
id.А(); // вызывается Base.А();
Однако если интерфейс реализуется с помощью виртуального метода класса, после его переопределения в потомке любой вариант обращения (через класс или через интерфейс) приведет к одному и тому же результату:
interface IBase
{
void А();
}
class Base: IBase
{
public virtual void А() {... }
}
class Derived: Base
{
public override void А() {... }
}
Derived d = new Derived();
d.A(); // вызывается Derived.А();
IBase id = d;
id.А(); // вызывается Derived.А();
Метод интерфейса, реализованный явным указанием имени, объявлять виртуальным запрещается. При необходимости переопределить в потомках его поведение пользуются следующим приемом: из этого метода вызывается другой, защищенный метод, который объявляется виртуальным. В приведенном далее примере метод А интерфейса IBase реализуется посредством защищенного виртуального метода А_, который можно переопределять в потомках класса Base:
interface IBase
{
void А();
}
class Base: IBase
{
void IBase.A() { A_(); }
protected virtual void А() {... }
}
class Derived: Base
{
protected override void А О {... }
}
Существует возможность повторно реализовать интерфейс, указав его имя в списке предков класса наряду с классом-предком, уже реализовавшим этот интерфейс. При этом реализация переопределенных методов базового класса во внимание не принимается:
interface IBase
{
void А();
}
class Base: IBase
{
void IBase.А() {... } //не используется в Derived
}
class Derived: Base, IBase
{
public void А() {... }
}
Если класс наследует от класса и интерфейса, которые содержат методы с одинаковыми сигнатурами, унаследованный метод класса воспринимается как реализация интерфейса, например:
interface Interface1
{
void F();
}
class Class1
{
public void F() {.. }
public void G() {... }
}
class Class2: Class1, Interface1
{
new public void G() {... }
}
Здесь класс Class2 наследует от класса Classl метод F. Интерфейс Interface1 также содержит метод F. Компилятор не выдает ошибку, потому что класс Class2 содержит метод, подходящий для реализации интерфейса.
Вообще при реализации интерфейса учитывается наличие «подходящих» методов в классе независимо от их происхождения. Это могут быть методы, описанные в текущем или базовом классе, реализующие интерфейс явным или неявным образом.