Интерфейсы — определение поведения для нескольких типов
Интерфейс содержит определения для группы связанных функциональных возможностей, которые не абстрактные class или должны struct реализовываться. Интерфейс может определять методы static , которые должны иметь реализацию. Начиная с C# 8.0, интерфейс может определять реализацию по умолчанию для членов. Интерфейс не может объявлять данные экземпляра, такие как поля, автоматические реализуемые свойства или события, подобные свойствам.
С помощью интерфейсов можно, например, включить в класс поведение из нескольких источников. Эта возможность очень важна в C#, поскольку этот язык не поддерживает множественное наследование классов. Кроме того, необходимо использовать интерфейс, если требуется имитировать наследование для структур, поскольку они не могут фактически наследовать от другой структуры или класса.
Интерфейс определяется с помощью ключевого interface слова, как показано в следующем примере.
Имя интерфейса должно быть допустимым именем идентификатора C#. По соглашению имена интерфейсов начинаются с заглавной буквы I .
Любой объект (класс или структура), реализующий интерфейс IEquatable<T>, должен содержать определение для метода Equals, соответствующее сигнатуре, которую задает интерфейс. В результате вы можете быть уверены, что класс, реализующий IEquatable<T> , содержит метод Equals , с помощью которого экземпляр этого класса может определить, равен ли он другому экземпляру того же класса.
Определение IEquatable<T> не предоставляет реализацию для метода Equals . Класс или структура может реализовывать несколько интерфейсов, но класс может наследовать только от одного класса.
Дополнительные сведения об абстрактных классах см. в разделе Абстрактные и запечатанные классы и члены классов.
Интерфейсы могут содержать методы экземпляра, свойства, события, индексаторы, а также любое сочетание этих четырех типов членов. Интерфейсы могут содержать статические конструкторы, поля, константы или операторы. Начиная с C# 11, элементы интерфейса, которые не являются полями, могут быть static abstract . Интерфейс не может содержать поля экземпляров, конструкторы экземпляров или методы завершения. Члены интерфейса по умолчанию являются общедоступными, и вы можете явно указать модификаторы доступа, такие как public , protected , internal , private , protected internal или private protected . Элемент private должен иметь реализацию по умолчанию.
Для реализации члена интерфейса соответствующий член реализующего класса должен быть открытым и не статическим, а также иметь такое же имя и сигнатуру, что и член интерфейса.
Когда интерфейс объявляет статические члены, тип, реализующий этот интерфейс, также может объявлять статические члены с той же сигнатурой. Они отличаются и однозначно идентифицируются типом, объявляющим элемент. Статический член, объявленный в типе , не переопределяет статический элемент, объявленный в интерфейсе.
Класс или структура, реализующая интерфейс, должна предоставлять реализацию для всех объявленных членов без реализации по умолчанию, предоставляемой интерфейсом. Однако если базовый класс реализует интерфейс, то любой класс, производный от базового класса, наследует эту реализацию.
В следующем примере показана реализация интерфейса IEquatable<T>. Реализующий класс Car должен предоставлять реализацию метода Equals.
Свойства и индексаторы класса могут определять дополнительные методы доступа для свойства или индексатора, определенного в интерфейсе. Например, интерфейс может объявлять свойство, имеющее акцессор get. Класс, реализующий этот интерфейс, может объявлять это же свойство с обоими акцессорами ( get и set). Однако если свойство или индексатор использует явную реализацию, методы доступа должны совпадать. Дополнительные сведения о явной реализации см. в статьях Явная реализация интерфейса и Свойства интерфейса.
Интерфейс может наследовать от одного или нескольких интерфейсов. Производный интерфейс наследует члены от своих базовых интерфейсов. Класс, реализующий производный интерфейс, должен реализовывать все члены в нем, включая все члены базовых интерфейсов производного интерфейса. Этот класс может быть неявно преобразован в производный интерфейс или любой из его базовых интерфейсов. Класс может включать интерфейс несколько раз через наследуемые базовые классы или через интерфейсы, которые наследуются другими интерфейсами. Однако класс может предоставить реализацию интерфейса только однократно и только если класс объявляет интерфейс как часть определения класса ( class ClassName : InterfaceName ). Если интерфейс наследуется, поскольку наследуется базовый класс, реализующий этот интерфейс, то базовый класс предоставляет реализацию членов этого интерфейса. Но производный класс может повторно реализовать любые члены виртуального интерфейса и не использовать наследованную реализацию. Когда интерфейсы объявляют реализацию метода по умолчанию, любой класс, реализующий этот интерфейс, наследует эту реализацию (необходимо привести экземпляр класса к типу интерфейса для доступа к реализации по умолчанию в члене интерфейса).
Базовый класс также может реализовывать члены интерфейса с помощью виртуальных членов. В таком случае производный класс может изменять поведение интерфейса путем переопределения виртуальных членов. Дополнительные сведения о виртуальных членах см. в статье Полиморфизм.
Зачем нужны интерфейсы c
Интерфейс представляет некое описание типа, набор компонентов, который должен иметь тип данных. И, собственно, мы не можем создавать объекты интерфейса напрямую с помощью конструктора, как например, в классах:
В конечном счете интерфейс предназначен для реализации в классах и структурах. Например, реализуем выше определенный интерфейс IMovable:
При применении интерфейса, как и при наследовании после имени класса или структуры указывается двоеточие и затем идут названия применяемых интерфейсов. При этом класс должен реализовать все методы и свойства применяемых интерфейсов, если эти методы и свойства не имеют реализации по умолчанию.
Если методы и свойства интерфейса не имеют модификатора доступа, то по умолчанию они являются публичными, при реализации этих методов и свойств в классе и структуре к ним можно применять только модификатор public .
Применение интерфейса в программе:
В данной программе определен метод DoAction() , который в качестве параметра принимает объект интерфейса IMovable. На момент написания кода мы можем не знать, что это будет за объект — какой-то класс или структура. Единственное, в чем мы можем быть уверены, что этот объект обязательно реализует метод Move и мы можем вызвать этот метод.
Иными словами, интерфейс — это контракт, что какой-то определенный тип обязательно реализует некоторый функционал.
Консольный вывод данной программы:
Реализация интерфейсов по умолчанию
Начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Зачем это нужно? Допустим, у нас есть куча классов, которые реализуют некоторый интерфейс. Если мы добавим в этот интерфейс новый метод, то мы будем обязаны реализовать этот метод во всех классах, применяющих данный интерфейс. Иначе подобные классы просто не будут компилироваться. Теперь вместо реализации метода во всех классах нам достаточно определить его реализацию по умолчанию в интерфейсе. Если класс не реализует метод, будет применяться реализация по умолчанию.
В данном случае интерфейс IMovable определяет реализацию по умолчанию для метода Move . Класс Person не реализует этот метод, поэтому он применяет реализацию по умолчанию в отличие от класса Car , который определяет свою реализацию для метода Move.
Стоит отметить, что хотя для объекта класса Person мы можем вызвать метод Move — ведь класс Person применяет интерфейс IMovable , тем не менее мы не можем написать так:
Множественная реализация интерфейсов
Интерфейсы имеют еще одну важную функцию: в C# не поддерживается множественное наследование, то есть мы можем унаследовать класс только от одного класса, в отличие, скажем, от языка С++, где множественное наследование можно использовать. Интерфейсы позволяют частично обойти это ограничение, поскольку в C# классы и структуры могут реализовать сразу несколько интерфейсов. Все реализуемые интерфейсы указываются через запятую:
Рассмотрим на примере:
В данном случае определены два интерфейса. Интерфейс IMessage определяет свойство Text, которое представляет текст сообщения. А интерфейс IPrintable определяет метод Print.
Класс Message реализует оба интерфейса и затем применяется в программе.
Интерфейсы в преобразованиях типов
Все сказанное в отношении преобразования типов характерно и для интерфейсов. Поскольку класс Message реализует интерфейс IMessage, то переменная типа IMessage может хранить ссылку на объект типа Message:
Преобразование от класса к его интерфейсу, как и преобразование от производного типа к базовому, выполняется автоматически. Так как любой объект Message реализует интерфейс IMessage.
Обратное преобразование — от интерфейса к реализующему его классу будет аналогично преобразованию от базового класса к производному. Так как не каждый объект IMessage является объектом Message (ведь интерфейс IMessage могут реализовать и другие классы), то для подобного преобразования необходима операция приведения типов. И если мы хотим обратиться к методам класса Message, которые не определены в интерфейсе IMessage, но являются частью класса Message, то нам надо явным образом выполнить преобразование типов:
Зачем придумали интерфейсы?
В интернете много статей на тему интерфейсов, что это такое и как их реализовывать. Но я не нашел внятного ответа кто и зачем их придумал? Я только начинаю изучать C# и вообще не вижу смысла в интерфейсах. Классы удобны например в ситуации когда нам нужно в игре создать много врагов, нам не надо каждому врагу прописовать параметры, можно все задать в классе. Вот пример с метанина:
Реализация в классе:
Но если я просто удалю интерфейс и сделаю вот так
Код по прежнему будет работать.
Вот и вопрос зачем все это нужно?
Я просто не могу представить себе ситуацию где бы было необходимо использовать интерфейсы.
В C#8 добавили реализацию метода по умолчанию в интерфейсе тем самым сделав из интерфейса недо-класс.
Смотрю видео на ютубе и там все используют интерфейсы но не говорят зачем, просто как будто так и надо.
Не спроста же все их используют? Но без понимания зачем все это нужно у меня просто не получается найти смысл их применения и понять как работает чужой код когда в нем есть интерфейсы.
Единственный смысл в них вижу в том что не нужно писать документацию к классам, просто наследуем от интерфейса а дальше пусть уже другой программист лезет в код и смотрит чего там и как.
Ни в коем случае не хочу сказать что я тут самый умный а все остальные ошибались, просто хочу разобраться почему придумали интерфейсы если и без них все хорошо работает?
Извините что так сумбурно написал, сам на эмоциях, неделю уже читаю статьи по теме и ни как не могу осилить.
![]()
![]()
Есть как минимум 4 причины для использования интерфейсов. Их может и больше, но я текст пишу из головы, потому остальные причины, если такие есть, загуглите или додумаете сами.
Давайте начнем с того, как строятся большие и сложные системы. Основной механизм построения чего то большого — это разделение этого большого на малые части, определения того, как эти части взаимодействуют, и после этого программирование каждой из частей.
Ключевой момент здесь в том, что вы не можете сразу продумать все эти мелкие части и сазу написать все классы для них.
Давайте возьмем типичный пример. Вы пишете свою игру и вам надо предусмотреть сохранение вашей игры. Вы точно знаете, что именно вам надо сохранить, но вы ещё не знаете, куда вы будете сохранять, в файл или в БД или передавать данные на сервер. Что делать в такой ситуации?
В такой ситуации вам поможет интерфейс. Например, у вас есть класс, который вам надо сохранить.
Напишем для него интерфейс сохранения
Что тут произошло? Вашего сохранения ещё в природе нет, кода для него нет, какой это будет класс, какая у класса-хранилища будет иерария наследования, ничего не известно, но вы уже определили, как ваша игра будет взаимодействовать с этим, ещё не существующим классом. Вы можете этот класс написать позже, или его может написать любой другой программист и все будет работать, так как контракт вашего будушего класса уже известен — вы уже определили с помощью интерфейса все нужные для класса методы.
Другими словами, интерфейс помогает определять взаимодействие между модулями вашей программы, даже если эти модули ещё не существуют.
Но тут вы можете возразить мне и сказать — что вы с таким же успехом можете определить класс для сохранения, например
Да, это верно, вы можете определить абстрактный класс. В таком случае, для реализации сохранения, вам надо будет унаследоваться от абстрактрого класса и реализовать нужные методы. Но давайте подумаем, какие ограничения это накладывает на наш класс сохранения? А вот какие — наш класс сохранения должен будет быть унаследован именно от указанного абстрактного класса и больше ни от кого. Вы можете возразить — мол, в случае интерфейса будет то же самое — ведь класс сохранения должен его реализовать. Но вот и нет — в C# реализация интерфейса никак не ограничивает класс, так как класс может реализовать сколько угодно интерфейсов. А вот наследование от абстрактного класса — уже дело другое, ведь вы не можете насловаться от 2 классов. Отсюда вторая причина использования интерфйсов — Использование интерфйсов позволяет обойти ограничения множественного наследования.
Окей, ну, допустим, мы решили, что мы будем сохранять нашу игру, но для сохранения нашей игры, нам обязательно надо знать имя текушего игрока — то есть его никнейм. Это требование леко выразить, добавив в наш абстрактный класс конструктор, который принимает имя игрока, например (код может не копилироваться, я его практически на телефоне пишу, но идея должна быть ясна)
Теперь посмотрите что произошло. Мы не только заставляем классы для сохранения игры наследоваться от нашего абстрактного класса, но ещё и накладываем на них определенные огреничения. Интересны ли эти ограничения остальной части игры? Думаю, нет. В таком случае, зачем остальной части игры знать об этих огреничениях? Нет никаких причин для этого. Таким образом, Используя интерфейс, вы декларируете желаемое поведение. Используя класс — вы декларируете детали реализации. То есть, когда у вас был интерфейс в игре, вы декларировали, что «не важно что тут будет за объект, не важно как он будет написан, но мне надо чтобы он мог сохранять и загружать состояние игры» — то есть вам было важно, что объект может делать, при этом не важно, как он это делает. В этом суть инкапсуляции. Когда же вы используете класс, особенно если класс содержит какие либо посторонние детали, вы уже покажываете, что вам важно не только что объект может делать, но и также кем объект является и какие у объекта есть детали реализации.
А теперь давайте посмотрим на это с точки зрения игры. Игре, на самом деле, не интересно знать, как и куда будет происходить сохранение. Игре не интересны детали сохранения. Игре не интересен даже факт — сущестует ли уже написанный класс сохранения. Все, что игре надо — это получить объект, который содержит нужные игре методы. Всё. При таком подходе связь между игрой и реальным классом сохранения лежит только через интерфейс, что является связью гораздо более слабой. Сам класс может быть написан как угодно, содержать какие угодно огранчения или детали реализации, эти ограничения и детали могут меняться и быть переписаны любое количество раз и все будет работать, пока класс сохранения реализует интерфейс. Таким образом, связь классов через интерфейс является более слабой связью, чем связь классов через абстрактный класс или напрямую, откуда следует выводы: