Предположим, у нас есть несколько классов, функциональность которого должна наследоваться другими. Тут все, кажется, просто — наследуем от базового класса и вперед. А если у нас есть несколько классов, сигнатуры методов которых должны наследоваться? Множественное наследование в C# отсутствует. Однако в данной ситуации нам могут пригодиться интерфейсы C#.
Чем же интерфейс отличается от обычного класса? В первую очередь, для его объявления используется ключевое слово interface. Кроме того, в интерфейсах методы только описываются. Реализация методов проводится в наследниках.
Объявление интерфейсов
Как говорилось выше, объявление интерфейса начинается со слова interface. Описываемые в интерфейсе свойства и методы не нуждаются в модификаторах доступа. Все свойства и методы являются открытыми. Однако сам интерфейс может быть как закрытым, так и открытым либо защищенным.
Рекомендуется каждый интерфейс размещать в отдельном файле с исходным кодом. Согласно правилам хорошего тона имена интерфейсов начинаются с буквы «I». В качестве примера рассмотрим реализацию интерфейса для персонажа.
/// <summary> /// Интерфейс персонажа /// </summary> public interface ICharacter { /// <summary> /// Уровень здоровья. /// </summary> double Health { get; set; } /// <summary> /// Ловкость. /// </summary> double Agility { get; set; } /// <summary> /// Сила. /// </summary> double Strength { get; set; } /// <summary> /// Атаковать персонажа. /// </summary> /// <param name="aim"> Цель атаки.</param> /// <param name="distance"> Расстояние до противника.</param> /// <returns></returns> void Attack(ICharacter aim, double distance); /// <summary> /// Защититься от атаки. /// </summary> /// <param name="damage"> Наносимый урон.</param> void Defend(double damage); }
Таким образом, мы объявили интерфейс с свойствами и методами, которые обязаны быть у каждого персонажа.
Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!
Реализация интерфейсов
Итак, интерфейс есть, а что дальше? Все достаточно просто, каждый класс, который наследует интерфейс, должен включать в себя реализацию описанных в интерфейсе методов. Относительно нашего примера, может быть реализован любой тип персонажа, от воина до серо-буро-малинового боевого единорога, каждый из которых по своему атакует и защищается.
/// <summary> /// Класс "Единорог". /// </summary> public class Unicorn : ICharacter { /// <summary> /// Уровень здоровья. /// </summary> public double Health { get; set; } /// <summary> /// Ловкость. /// </summary> public double Agility { get; set; } /// <summary> /// Сила. /// </summary> public double Strength { get; set; } /// <summary> /// Атаковать персонажа. /// </summary> /// <param name="aim"> Цель атаки.</param> /// <param name="distance"> Расстояние до противника.</param> public void Attack(ICharacter aim, double distance) { aim.Defend(Strength * int.MaxValue / 1000); } /// <summary> /// Защититься от атаки. /// </summary> /// <param name="damage"> Наносимый урон.</param> public void Defend(double damage) { Health -= damage / 1000; } }
Используем реализованные интерфейсы
Реализация интерфейса может использоваться несколькими способами. Наиболее явным является использование объекта класса, наследующего интерфейс. Другим, несколько более интересным примером использования является объявление переменной интерфейсного типа. Так, подобная переменная может стать объектом любого класса, наследующего интерфейс.
ICharacter greenUnicorn = new Unicorn() { Health = 100, Agility = 100, Strength = 100 }; Unicorn rainbowUnicorn = new Unicorn() { Health = 1000, Agility = 500, Strength = 700 }; greenUnicorn.Attack(rainbowUnicorn, 1);
Таким образом, абсолютно не важно, объект какого класса находится в переменной, главное чтобы объект реализовывал интерфейс.
Более того, интерфейсы могут передаваться в качестве параметров. Как вы могли заметить, в методе Attack передается интерфейсная переменная. Так, мы получаем дополнительную универсальность методов. Как было сказано ранее, подобная переменная может содержать объект любого класса реализующего интерфейс.
Перегрузка интерфейсных методов
Допустим, в классе и в интерфейсе есть метод с одинаковым именем и набором переменных. Получается, в классе должно быть два метода с одинаковыми сигнатурами. А это противоречит правилам и вызовет ошибку компиляции. Однако в случае с методами интерфейсов есть обходной путь.
/// <summary> /// Класс "Единорог". /// </summary> public class Unicorn : ICharacter { /// <summary> /// Уровень здоровья. /// </summary> public double Health { get; set; } /// <summary> /// Ловкость. /// </summary> public double Agility { get; set; } /// <summary> /// Сила. /// </summary> public double Strength { get; set; } /// <summary> /// Атаковать персонажа. /// </summary> /// <param name="aim"> Цель атаки.</param> /// <param name="distance"> Расстояние до противника.</param> void ICharacter.Attack(ICharacter aim, double distance) { aim.Defend(Strength * int.MaxValue / 1000); } /// <summary> /// Защититься от атаки. /// </summary> /// <param name="damage"> Наносимый урон.</param> public void Defend(double damage) { Health -= damage / 1000; } /// <summary> /// Атаковать персонажа. /// </summary> /// <param name="aim"> Цель атаки.</param> /// <param name="distance"> Расстояние до противника.</param> public void Attack(ICharacter aim, double distance) { aim.Defend(Strength * 10000); } }
Как вы могли заметить, в классе появился новый метод ICharacter.Attack.Так, в классе Unicorn метод Attack объявлен дважды. Чтобы обратиться к методу интерфейса, нужно явно указать, что нам нужен именно он. Для этого в примере использования реализации интерфейса заменим вызов метода Attack на следующий.
((ICharacter)greenUnicorn).Attack(rainbowUnicorn, 1);
Наследование
Итак, мы знакомы с наследованием классов, а есть ли отличия для интерфейсов? По сути, принципиальной разницы нет. Интерфейс может наследовать другой интерфейс, при этом наследуются все свойства и методы базового. Притом, при реализации интерфейса-наследника необходимо реализовать все свойства и методы. Следует заметить, наследоваться от классов интерфейсы не могут ни при каких обстоятельствах.
Кроме того, рекомендую прочитать статью Работа с XML в C#. А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.