Поиск

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

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

using System;
public class Control {
public void SerializeO
<
Console.WriteLine("Control.Serialize called");
> >
public interface IDataBound {
void SerializeO;
}
public class EditBox : Control, IDataBound
{
>
class InterfacelnhlApp
{
public static void Main()
{
EditBox edit = new EditBoxQ; edit. SerializeO; } }

Как вы знаете, чтобы реализовать интерфейс, нужно предоставить определение каждого члена в определении интерфейса. Однако в предыдущем примере мы этого не сделали, а код все равно компилируется! Причина в том, что компилятор С# ищет метод Serialize, реализованный в классе EditBox, и находит его. Однако компилятор неверно определяет, что это реализованный метод. Метод Serialize, найденный компилятором, унаследован от класса Control методом Serialize, но не является настоящей реализацией метода IDataBound.Serialize. Поэтому, хоть он и компилируется, этот код не будет функционировать, как задумано, в чем мы убедимся позже.

Теперь внесем дополнительную интригу. Следующий код сначала проверяет оператором as, реализован ли интерфейс, затем пытается вызвать реализованный метод Serialize. Этот код компилируется и работает. Однако, как мы знаем, в классе EditBox метод Serialize на самом деле не реализован из-за наследования IDataBound. В EditBox уже есть метод Serialize (унаследованный) от класса Control. Это значит, что клиент, по всей вероятности, не получит ожидаемых результатов.

using System;
public class Control {
public void SerializeO
{
Console.WriteLine("Control.Serialize called");
} }
public interface IDataBound {
void SerializeO; }
public class EditBox : Control, IDataBound
{
}
class InterfaceInh2App {
public static void MainО
<
EditBox edit = new EditBoxO;
IDataBound bound = edit as IDataBound;
if (bound != null)
{
Console.WriteLine("IDataBound is supported...");
bound. SerializeO; }
else {
Console.WriteLineC'IDataBound is NOT supported...");
>
> >

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

using System;
interface ITest
{
void Foo();
}
// В классе Base реализован интерфейс ITest. class Base : ITest
<
public void Foo()
{
Console.WriteLine("Base.Foo (ITest implementation)");
> }
class HyDerived : Base
{
public new void Foo()
{
Console.WriteLine("MyDerived.Foo");
} }
public class InterfacelnhSApp
{
public static void MainQ
{
MyDe rived myDe rived = new MyDerivedO;
myDerived.Foo();
ITest test = (ITest)myDerived; test.FooQ; }
}
В результате выполнения этот код выводит информацию:
MyDerived.Foo
Base.Foo (ITest implementation)

В этой ситуации в классе Base реализован интерфейс ITest и его метод Foo. Однако производный от Base класс My Derived реализует для этого класса новый метод Foo. Какой из методов Foo будет вызван? Это зависит от имеющейся у вас ссылки. Если есть ссылка на объект My Derived, вызывается его метод Foo. Это так, даже несмотря на то, что у объекта myDerived есть унаследованная реализация ITest.Foo: в период выполнения будет исполнен MyDerived.Foo, так как ключевым словом new задана подмена унаследованного метода.

Однако когда вы явно выполняете приведение объекта myDerived к интерфейсу ITest, компилятор разрешает реализацию интерфейса. У класса MyDerived есть метод с тем же именем, но это не тот метод, что ищет компилятор. Когда вы приводите объект к интерфейсу, компилятор проходит по дереву наследования, пока не найдет класс, содержащий в своем базовом списке интерфейс. Именно поэтому в результате выполнения последних двух строк кода метода Main вызывается метод Foo, реализованный в ITest.

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