Интерфейсы в C#

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

Можно встретить достаточно много различных определений, что такое интерфейс в программировании и, в частности, в языке C#. От самых общих, например, «интерфейс — это то по средствам чего что-то взаимодействует с чем-то». До более понятных и конкретных: интерфейс — это контракт о взаимодействии между различными частями системы. При этом, под «системой» понимается не только одна единственная программа, но и совокупность программных средств, разработанных, в том числе, и разными группами разработчиков. Тема интерфейсов в C# — это довольно интересная и, что главное, одна из наиболее важных тем в изучении основ программирования на C#. И сегодня мы начнем эту тему изучать.

Язык C# — это довольно динамично развивающийся язык программирования и очередные нововведения, например, в C# 8.0 достаточно серьезно меняют концепцию интерфейсов, в принципе, однако, мы будем разбирать тему постепенно — от самых основ и до последних нововведений. Начнем как полагается — с классики.

Интерфейсы в C#

До выхода C# 8.0, что такое интерфейс, с точки зрения программирования, можно было определить следующим образом — это именованная область кода, содержащая сигнатуры методов, свойств, индексаторов и событий. То есть, интерфейс, в его классическом понимании и представлении, не должен содержать никаких реализаций чего-либо — только сигнатуры.

Выше, мы определились с тем, что интерфейс — это также некий контракт о взаимодействии. Зачем такой «контракт» нужен и когда его следует использовать?

Рассмотрим следующую задачу: нам необходимо наделить несколько классов одними и теми же методами. Здесь мы можем пойти следующим путем: если эти классы наши и классы родственны по своей сути, то мы можем выделить для них класс-предок, в котором реализовать необходимые нам методы и, соответственно, все наследники эти методы унаследуют и, при необходимости перегрузят или переопределят. Вполне простой и понятный ход работы. Но как решать такую задачу, если: а) классы не родственны по своей сути; б) мы хотим, чтобы нашим кодом (например, библиотекой классов) могли воспользоваться другие разработчики и написать свои реализации методов? И именно с этих двух моментов и начинается практическая значимость интерфейсов в C#.

Объявление интерфейса в C#

Для того, чтобы объявить интерфейс в C# используется ключевое слово interface. В общем случае, объявление интерфейса выглядит следующим образом:

interface имя_интерфейса
{
    сигнатуры методов;
    сигнатуры свойств и т.д.
}

Имя интерфейса обычно начинается с заглавной буквы I. Например, вы можете встретить такие названия интерфейсов .NET как IComparable, IEnumerable и т.д. Такое именование сразу дает нам понять, что перед нами находится интерфейс, а не класс. Технически, мы можем задать вообще любое имя интерфейса, не используя I в имени, но лучше придерживаться правил.

Для примера работы с интерфейсами в C# рассмотрим следующую ситуацию: есть человек, магазин и банк. Три неродственные сущности. И у человека, и у магазина и у банка в распоряжении могут находиться деньги, например, у человека деньги хранятся в кошельке, у магазина — в кассе, у банка — в сейфе. Нам необходима сделать так, чтобы у всех трех классов были одинаковые методы работы с деньгами: метод, позволяющий положить деньги, например, в кошелек и метод, позволяющий изъять деньги из кошелька. Для этого объявим вот такой интерфейс:

interface IMoneyVault
{
    int MoneyAdd(int count);
    int MoneyRemove(int count);
}

Как можно видеть, наш интерфейс, во-первых, не содержит никаких реализаций методов — только сигнатуры и, во-вторых, методы интерфейса не имеют никаких модификаторов доступа — все они по умолчанию публичны и имеют модификатор public(по крайней мере до версии C# 8.0). После того, как мы объявили интерфейс, классы его (интерфейс) реализующие должны в обязательном порядке реализовать всё, что есть у интерфейса. В нашем случае — это два метода: MonewAdd и MonewRemove.

Реализация интерфейсов в C#

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

Интерфейсы в C#

Обратите внимание, что Visual Studio сразу же, как только вы напишете имя реализуемого интерфейса, подчеркнет его, а в списке ошибок появятся одна или несколько ошибок следующего содержания:

Ошибка CS0535 «Person» не реализует член интерфейса «IMoneyVault.MoneyAdd(int)«.

Такие ошибки будут появляться до тех пор, пока мы не реализуем всё, что есть в интерфейсе. Напишем следующие реализации методов:

public class Person : IMoneyVault
{
    private int currentMoney;

    public int MoneyAdd(int count)
    {
        return currentMoney += count;
    }

    public int MoneyRemove(int count)
    {
        return currentMoney -= count;
    }
}

Конечно, здесь немного теряется логика в том плане, что у нас человек, образно говоря, становится хранилищем денег (реализует интерфейс IMoneyVault) и, по-хорошему, надо было бы назвать наш класс несколько иначе, но для примера оставим всё так как есть. После того, как интерфейс реализован, мы можем использовать его методы. Например:

Person Person = new Person();//создаем объект
Console.WriteLine(Person.MoneyAdd(10)); //добавили деньги
Console.WriteLine(Person.MoneyRemove(5));//изъяли деньги
10
5

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

public class Shop : IMoneyVault
{
    private int currentMoney;

    public int CurrentMoney { get=>currentMoney; }

    private double nalog;
    public double Nalog { get=>nalog; }


    public int MonewAdd(int count)
    {
        currentMoney += count;
        nalog += 0.13 * currentMoney;
        return currentMoney;
    }

    public int MonewRemove(int count)
    {
        currentMoney -= count;
        nalog -= 0.13 * currentMoney;
        return currentMoney;
    }
}

Проверим работу:

Shop Shop = new Shop();
Shop.MoneyAdd(1000);
Console.WriteLine($"Всего денег: {Shop.CurrentMoney}"); 
Console.WriteLine($"В том числе НДС: {Shop.Nalog}");
Всего денег: 1000
В том числе НДС: 130

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

public double MonewAdd(int count)
 {
     currentMoney += count;
     nalog += 0.13 * currentMoney;
     return currentMoney;
 }

так как в интерфейсе этот метод имеет другую сигнатуру — возвращаемое значение должно быть int, а не double.

Использование интерфейсов в C#

В примерах выше, мы явно создавали класс Person или Shop и вызывали методы. Однако, это не единственный способ использования интерфейсов в C#. Рассмотрим такой пример:

Shop Shop = new Shop(); 
object data = Shop; //теперь просто так вызвать MoneyAdd нельзя
IMoneyVault moneyVault = Shop; //создавать интерфейсные переменные - можно

if (data is IMoneyVault)
{ 
    int money = ((IMoneyVault)data).MoneyAdd(10);
    Console.WriteLine(money);
}

int money2 = moneyVault.MoneyAdd(100);
Console.WriteLine(money2);

Здесь происходит следующее:

  • вначале мы создаем переменную типа Shop так как мы привыкли это делать;
  • далее, мы присваиваем полученное значение переменой типа object, т.е. переменной самого базового типа.
  • и, наконец, мы создаем переменную типа интерфейса. Сам интерфейс мы так создать не можем, т.к. конструктора у него нет никакого (даже по умолчанию), то есть вот такой код вызовет ошибку:
IMoneyVault vault = new IMoneyVault();
Ошибка CS0144 Не удается создать экземпляр абстрактного типа или интерфейса «IMoneyVault»

но, при этом, в C# мы можем завести переменную интерфейсного типа и спокойно присваивать такоё переменной значения, которое является любым объектом, реализующим указанный интерфейс, что мы и сделали. Далее, так как переменная data, хоть и хранит ссылку на Shop, но о методах интерфейса эта переменная уже ничего не знает, поэтому мы:

  1. используя ключевое слово is проверяем действительно ли в переменной дата содержится объект класса, реализующего интерфейс IMoneyVault
  2. вызываем метод MoneyAdd

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

10
110

так как переменные data и moneyVault по сути содержат ссылки на один и тот же объект Shop.

Итого

Сегодня мы рассмотрели основные моменты работы с интерфейсами в C#. Научились объявлять интерфейсы и реализовывать их в наших классах. Всё, что мы рассмотрели в этой части справедливо для всех версий .NET и C#, однако, возможности интерфейсов были значительно расширены в C# 8.0 и более новых версиях, о чем мы и поговорим в следующих частях учебника.

 

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
1 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии