На этом шаге мы приведем общие сведения об интерфейсах.
На предыдущих шагах мы познакомились с базовыми принципами использования абстрактных классов. Как уже несколько раз отмечалось, использование абстрактного класса в качестве базового позволяет создавать производные классы "по одному шаблону" - то есть с одинаковым набором свойств и методов. Вместе с тем здесь имеется один "тонкий момент". Дело в том, что в языке C# запрещено множественное наследование: мы не можем создать производный класс на основе сразу нескольких базовых.
Это не очень хорошо, поскольку часто возникает необходимость "объединить в одно целое" сразу несколько классов. Например, в языке C++, ставшем "прародителем" для языка C#, такая возможность существует. Это полезная возможность, но одновременно это и небезопасная возможность. Ведь разные классы описывались независимо друг от друга. Их объединение в один класс может привести к конфликтным ситуациям. Поэтому в языке C# от технологии множественного наследования отказались. Вместо множественного наследования используется другая технология, связанная с реализацией интерфейсов.
Главная опасность в попытке объединения классов связана с тем, что в них есть методы и эти методы каким-то образом определены. Когда метод описывался в классе, перспектива совместного использования этого метода с методами из иных классов, скорее всего, не рассматривалась. Отсюда и неприятные сюрпризы. Но если объединять классы с абстрактными методами, то данная проблема снимается автоматически, поскольку абстрактные методы не имеют тела, они только объявлены (но не описаны). Необходимо только обеспечить, чтобы все методы были абстрактными. В обычном абстрактном классе в общем случае это не так. Отсюда появляется потребность в интерфейсах.
Интерфейс представляет собой блок из абстрактных методов, свойств и индексаторов. Фактически это аналог абстрактного класса. Но, в отличие от абстрактного класса, в интерфейсе абсолютно все абстрактное. Описывается интерфейс специальным образом, хотя описание интерфейса и напоминает описание класса. Общий шаблон описания интерфейса представлен ниже:
interface имя { // Тело интерфейса }
Начинается описание интерфейса с ключевого слова interface, после которого указывается название интерфейса, а в блоке из фигурных скобок объявляются методы, индексаторы и свойства.
Для методов указывается только сигнатура: тип результата, название метода и список аргументов. Ключевое слово abstract не указывается, как и ключевое слово virtual. По умолчанию объявленные в интерфейсе методы (а также свойства и индексаторы) считаются абстрактными и виртуальными. Спецификатор уровня доступа также не указывается. Все методы (свойства, индексаторы), объявленные в интерфейсе, являются открытыми (то есть будто бы описанными с ключевым словом public, хотя оно явно не используется).
Интерфейс нужен для того, чтобы на его основе создавать классы. Если класс создается на основе интерфейса, то говорят, что класс реализует интерфейс. Реализация интерфейса в классе подразумевает, что в этом классе описаны все методы, свойства и индексаторы, которые объявлены в интерфейсе. Причем при описании методов, свойств и индексаторов в классе ключевое слово override не используется.
Имя интерфейса, реализуемого в классе, указывается в описании класса через двоеточие после имени класса (то есть так же, как указывается имя базового класса при наследовании). Шаблон описания класса, реализующего интерфейс, следующий:
class имя: интерфейс { // Тело класса }
Реализация интерфейса напоминает наследование абстрактного класса. Но базовый класс может быть только один, а вот что касается реализации интерфейсов, то в одном классе может реализоваться больше одного интерфейса. Если класс реализует несколько интерфейсов, то эти интерфейсы перечисляются через запятую (после двоеточия) в описании класса:
class имя: интерфейс1, интерфейс2, ..., интерфейсN { // Тело класса }
Наследование базового класса (абстрактного или обычного) и реализация интерфейсов могут использоваться одновременно. В этом случае в описании класса после имени класса и двоеточия сначала указывается имя базового класса, а затем через запятую перечисляются реализуемые в классе интерфейсы:
class имя: базовый класс, интерфейс1, интерфейс2, ..., интерфейсN { // Тело класса }
Если так, то в классе должны быть описаны все методы, свойства и индексаторы, объявленные в реализуемых интерфейсах, а если наследуемый базовый класс абстрактный - то и все абстрактные методы из базового класса.
На следующем шаге мы продолжим изучение этого вопроса.