• Добро пожаловать на сайт - Forumteam.digital !

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

    Группа в телеграме (подпишитесь, что бы не потерять нас) - ForumTeam Chat [Подписатся]
    Связь с администратором - @ftmadmin

22. Работаем с интерфейсами.

Redman

<FORUMTEAM>
Команда форума
Регистрация
13.11.17
Сообщения
13.340
Реакции
8.377
Баллы
1.200
FTC
208¢
Интерфейсы.

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

Прежде всего введем понятие контракта. Контракт - это набор четко определенных условий, которые регулируют отношения между классом-сервером и его клиентами; включает индивидуальные контракты для всех экспортируемых членов класса и глобальные свойства класса.

Интерфейс определяет контракт, а классы и структуры, реализующие данный интерфейс, должны четко придерживаться данного контракта. Другими словами интерфейс - это набор описаний методов и свойств, которые реализующий интерфейс класс или структура (класс иди структура наследуют данный интерфейс) обязаны определить. Таким образом, интерфейс напоминает абстрактный класс, у которого все методы абстрактны.

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

Пример интерфейса, описывающего методы для масштабирования объекта.
Код:
interface IScalable
{
    void ScaleX(double sx);
    void ScaleY(double sy);
}

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

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

Интерфейсы и наследование
Вам уже известно, что в языке C# разрешено только простое наследование (наследуется только один класс). В случае с интерфейсами разрешено и простое и множественное наследование.
Код:
interface IA
{
    void f();
}
interface IB
{
    void g();
}
class C : IA, IB
{
    // обязательная реализация функций f и g
    public void f()
    {
      ...
    }
    public void g()
    {
     ...
    }
}

В случае, если класс наследует некий базовый класс и реализует один или несколько интерфейсов, в линии наследования класс ставится на первое место.
Код:
interface IA
{
    void f();
}
interface IB
{
    void g();
}
class Base
{
...
}
class C : Base, IA, IB
{
    // обязательная реализация функций f и g
    public void f()
    {
      ...
    }
    public void g()
    {
     ...
    }
}

При преобразовании объекта к интерфейсу в иерархии наследования ищется класс, у которого в списке базовых классов указан этот интерфейс. Одного наличия правильных функций недостаточно.
Код:
interface IF
{
    void F();
}

class T : IF
{
    public void F()
    {
        Console.WriteLine("T");
    }
    public T()
    {
        F();
    }
}

class TDerived : T
{
    public new void F()
    {
        Console.WriteLine("TDerived");
    }
    public TDerived()
    {
        F();
    }
}

class Test
{
    static void Main()
    {
        T td = new TDerived();

        // Преобразование объекта к интерфейсу       
        IF iif = (IF)td;
        iif.F();
    }
}

Вывод на экран:

Проведем анализ кода и результата. Сначала создается ссылка типа базового класса T, а затем выделяется память под объект производного класса TDerived, соответственно, вызываются конструктор базового и производных классов (на экране T и TDerived).

Затем создается ссылка типа интерфейса IF, которая получает адрес объекта td, приведенного к интерфейсу IF (кстати, такое преобразование имеет смысл только в том случае, если приводимый объект реализует данный интерфейс или данный интерфейс реализует один из его предков). Затем вызывается функция F из класса T (на экране T). Почему? Ведь td ссылается на класс TDerived. Дело в том, что класс TDerived в явном виде не реализует интерфейс IF, хотя и имеет соответствующую функцию. Для того чтобы вызывалась "нужная" функция, пример необходимо переписать следующим образом:
Код:
interface IF
{
    void F();
}

class T : IF
{
    public void F()
    {
        Console.WriteLine("T");
    }
    public T()
    {
        F();
    }
}

class TDerived : T, IF
{
    public new void F()
    {
        Console.WriteLine("TDerived");
    }
    public TDerived()
    {
        F();
    }
}

class Test
{
    static void Main()
    {
        T td = new TDerived();

        // Преобразование объекта к интерфейсу       
        IF iif = (IF)td;
        iif.F();
    }
}

Вывод на экран:

*******
Если некоторый интерфейс реализуется классом, возможно неявное преобразование ссылки на экземпляр класса в ссылку на интерфейс. Такое преобразование относится к неявным, поскольку на этапе компиляции заведомо известно, что оно пройдет успешно.
*******
Код:
class Test
{
    static void Main()
    {
        T td = new TDerived();

        // Неявное преобразование объекта к интерфейсу       
        IF iif = td;
        iif.F();
    }
}

Оператор is

Перепишем предыдущий пример следующим образом:
Код:
interface IF
{
    void F();
}

class T
{
    public void F()
    {
        Console.WriteLine("T");
    }
    public T()
    {
        F();
    }
}

class TDerived : T, IF
{
    public new void F()
    {
        Console.WriteLine("TDerived");
    }
    public TDerived()
    {
        F();
    }
}

class Test
{
    static void Main()
    {
        T [] tarr = new T[4];
        tarr[0] = new T();
        tarr[1] = new TDerived();
        tarr[2] = new T();
        tarr[3] = new TDerived();
       
        foreach(T t in tarr)
        {
            IF iif = (IF)t;
            iif.F();
        }   
    }
}

Результатом выполнения данного примера будет ошибка времени выполнения. В чем проблема? А она кроется в строке

Дело в том, что элемент tarr[0] ссылается на объект класса T, который не реализует интерфейс IF. Следовательно, при попытке приведения мы получим исключение типа InvalidCastException. В языке C# существует возможность проверить реализует ли данный класс данный интерфейс с помощью оператора is. Задача данного оператора проверить совместим ли тип (времени выполнения) объекта с данным типом. Перепишем пример снова:
Код:
interface IF
{
    void F();
}

class T
{
    public void F()
    {
        Console.WriteLine("T");
    }
    public T()
    {
        F();
    }
}

class TDerived : T, IF
{
    public new void F()
    {
        Console.WriteLine("TDerived");
    }
    public TDerived()
    {
        F();
    }
}

class Test
{
    static void Main()
    {
        T [] tarr = new T[4];
        tarr[0] = new T();
        tarr[1] = new TDerived();
        tarr[2] = new T();
        tarr[3] = new TDerived();
       
        foreach(T t in tarr)
        {
            if(t is IF) // Проверка
            {
                IF iif = (IF)t;
                iif.F();
            }
        }   
    }
}

Вывод на экран:
T
T
TDerived
T
T
TDerived
TDerived
TDerived

*******
Можно было, конечно, отловить сбой при помощи обработки исключений, но это слишком загромождает программу.
*******

Оператор as

Если вы заметили, в предыдущем примере выполняется лишнее действие, связанное с тем, что тип объекта проверяется дважды - при выполнении оператора is и при приведении типа. Поэтому в предыдущем примере разумнее воспользоваться оператором as, задача которого попытаться осуществить приведение типов без возбуждения исключительной ситуации (в случае невозможности приведения оператор возвратит null). И снова перепишем пример:
Код:
interface IF
{
    void F();
}

class T
{
    public void F()
    {
        Console.WriteLine("T");
    }
    public T()
    {
        F();
    }
}

class TDerived : T, IF
{
    public new void F()
    {
        Console.WriteLine("TDerived");
    }
    public TDerived()
    {
        F();
    }
}

class Test
{
    static void Main()
    {
        T [] tarr = new T[4];
        tarr[0] = new T();
        tarr[1] = new TDerived();
        tarr[2] = new T();
        tarr[3] = new TDerived();
       
        foreach(T t in tarr)
        {
            IF iif = t as IF;
            if(iif != null) // Проверка
            {
                iif.F();
            }
        }   
    }
}

Вывод на экран:
T
T
TDerived
T
T
TDerived
TDerived
TDerived

*******
Операторы is и as могут применяться к классам.
*******

Явная реализация интерфейсов

В некоторых случаях вы можете захотеть скрыть реализацию интерфейса от пользователей класса. В этом случае можно применить технологию явной реализации интерфейсов.

Пример:
Код:
interface IA
{
    void f();
}
class C : IA
{
   // Явная реализация функции интерфейса;
   // режим доступа указывать запрещается
   void IA.f()
   {
      ...
   }
}
class Test
{
    static void Main()
    {
        C c = new C();
        IA a = (IA)c;
        // Вызов данной функции интерфейса возможен только
        // посредством явного преобразования объекта к
        // соответствующему интерфейсу
        a.f();
    }
}

Интерфейсы, основанные на других интерфейсах

Интерфейсы можно объединять, образуя новые интерфейсы.
Код:
interface IA
{
    void f();
}
interface IB
{
    void g();
}
interface IC : IA, IB
{
    void h();
}

Данный код означает, что класс, реализующий интерфейс IC, должен будет реализовать все три функции интерфейса (f, g, и h).

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