Интерфейсы: назначение, правила написания. Способы реализации интерфейсов.
Работа с объектами через интерфейсы. Операторы is и as.
Интерфейс представляет ссылочный тип, который может определять некоторый функционал (набор методов и свойств без реализации). Затем этот функционал реализуют классы и структуры, которые применяют данные интерфейсы.
Для определения интерфейса используется ключевое слово interface . Названия интерфейсов в C# начинаются с заглавной буквы I , например, IComparable, IEnumerable (так называемая венгерская нотация), однако это не обязательное требование , а стиль программирования .
Интерфейсы могут определять следующие сущности:
- Методы
- Свойства
- Индексаторы
- События
- Статические поля и константы (начиная с версии C# 8.0)
Интерфейсы не могут определять переменные.
Например, простейший интерфейс, который определяет все эти компоненты:
interface IMovable
{
// константа
const int minSpeed = 0; // минимальная скорость
// статическая переменная
static int maxSpeed = 60; // максимальная скорость
// метод
void Move(); // движение
// свойство
string Name { get; set; } // название
delegate void MoveHandler(string message); // определение делегата для события
// событие
event MoveHandler MoveEvent; // событие движения
}
В примере определен интерфейс IMovable, который представляет некоторый движущийся объект. Данный интерфейс содержит различные компоненты, которые описывают возможности движущегося объекта. То есть интерфейс описывает некоторый функционал, который должен быть у движущегося объекта.
Методы и свойства интерфейса могут не иметь реализации, в этом они сближаются с абстрактными методами и свойствами абстрактных классов.
В данном случае интерфейс определяет метод Move, который будет представлять некоторое передвижение. Он не имеет реализации, не принимает никаких параметров и ничего не возвращает.
То же самое в данном случае касается свойства Name - это определение свойства в интерфейсе, которое не имеет реализации.
При объявлении интерфейса, если его члены (методы и свойства) не имеют модификаторов доступа, то по умолчанию доступ public , так как цель интерфейса - определение функционала для реализации его классом. Это касается также и констант и статических переменных, которые в классах и структурах по умолчанию имеют модификатор private . В интерфейсах же они имеют по умолчанию модификатор public .
Например, мы могли бы обратиться к константе minSpeed и переменной maxSpeed интерфейса IMovable:
static void Main(string[] args)
{
Console.WriteLine(IMovable.maxSpeed);
Console.WriteLine(IMovable.minSpeed);
}
Начиная с версии C# 8.0, можно явно указывать модификаторы доступа у компонентов интерфейса:
interface IMovable
{
public const int minSpeed = 0; // минимальная скорость
private static int maxSpeed = 60; // максимальная скорость
public void Move();
protected internal string Name { get; set; } // название
public delegate void MoveHandler(string message); // //определение делегата для события
public event MoveHandler MoveEvent; // событие движения
}
Начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Можно определить в интерфейсах методы и свойства, которые имеют реализацию как в обычных классах и структурах.
Например, определим реализацию метода Move по умолчанию:
interface IMovable
{
// реализация метода по умолчанию
void Move()
{
Console.WriteLine("Walking");
}
}
Реализацией свойства по умолчанию в интерфейсах сложнее, так как нельзя определять в интерфейсах нестатические переменные, соответственно в свойствах интерфейса мы не можем манипулировать состоянием объекта. Реализацию по умолчанию для свойств можно определить следующим образом:
interface IMovable
{
void Move()
{
Console.WriteLine("Walking");
}
// реализация свойства по умолчанию
// свойство только для чтения
int MaxSpeed { get { return 0; } }
}
Если интерфейс имеет приватные методы и свойства (то есть с модификатором private), то они должны иметь реализацию по умолчанию. Это относится к любым статическим методам и свойствам (не обязательно приватным):
distance / speed; static int MaxSpeed { get { return maxSpeed; } set { if (value 0) maxSpeed = value; } } } " width="640"
interface IMovable
{
public const int minSpeed = 0; // минимальная скорость
private static int maxSpeed = 60; // максимальная скорость
// находим время, за которое надо пройти расстояние distance со скоростью speed
static double GetTime(double distance, double speed) = distance / speed;
static int MaxSpeed
{
get { return maxSpeed; }
set
{
if (value 0) maxSpeed = value;
}
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(IMovable.MaxSpeed);
IMovable.MaxSpeed = 65;
Console.WriteLine(IMovable.MaxSpeed);
double time = IMovable.GetTime(100, 10);
Console.WriteLine(time);
}
}
Модификаторы доступа интерфейсов
Интерфейсы по умолчанию имеют уровень доступа internal , то есть такой интерфейс доступен только в рамках текущего проекта. Но с помощью модификатора public мы можем сделать интерфейс общедоступным:
New Item... и в диалоговом окне добавления нового компонента выбрать пункт Interface . " width="640"
public interface IMovable
{
void Move();
}
В Visual Studio есть специальный компонент для добавления нового интерфейса в отдельном файле. Для добавления интерфейса в проект можно нажать правой кнопкой мыши на проект и в появившемся контекстном меню выбрать Add - New Item... и в диалоговом окне добавления нового компонента выбрать пункт Interface .
Реализация интерфейса
Чтобы указать, что класс реализует интерфейс, необходимо, после имени класса и двоеточия указать имя интерфейса:
class SomeClass : ISomeInterface // реализация интерфейса //ISomeInterface { // тело класса }
Класс, который реализует интерфейс, должен предоставить реализацию всех членов интерфейса:
class SomeClass : ISomeInterface { public string SomeProperty { get { …. }
set { …
} } public void SomeMethod(int a) { // тело метода } }
Пример: Есть классы геометрических фигур Прямоугольник и Окружность . У обоих классов должны быть методы вычисления периметра и площади. Эти методы мы представим интерфейсом :
interface IGeometrical // объявление интерфейса { void GetPerimeter(); void GetArea (); } class Rectangle : IGeometrical //реализация интерфейса { public void GetPerimeter() { Console.WriteLine("(a+b)*2"); } public void GetArea() { Console.WriteLine("a*b"); } }
class Circle : IGeometrical //реализация интерфейса { public void GetPerimeter() { Console.WriteLine("2*pi*r"); } public void GetArea() { Console.WriteLine("pi*r^2"); } } class Program { static void Main(string[] args) { List figures = new List(); figures.Add(new Rectangle()); figures.Add(new Circle()); foreach (IGeometrical f in figures) { f.GetPerimeter(); f.GetArea(); } Console.ReadLine(); } }
Множественное наследование – это когда один класс сразу наследуется от нескольких классов. Но бывает так, что базовые классы содержат методы с одинаковыми именами, в результате чего возникают определенные неточности и ошибки.
Множественное наследование есть в языке C++, а в C# от него отказались и вместо этого ввели интерфейсы.
В C# класс может реализовать сразу несколько интерфейсов. Это и является главным отличием использования интерфейсов и абстрактных классов. Кроме того, абстрактные классы могут содержать все остальные члены, которых не может быть в интерфейсе, и не все методы/свойства в абстрактном классе должны быть_абстрактными.
Если класс реализует несколько интерфейсов, они разделяются_запятыми:
interface IDrawable { void Draw(); } interface IGeometrical { void GetPerimeter(); void GetArea (); } class Rectangle : IGeometrical, IDrawable { public void GetPerimeter() { Console.WriteLine("(a+b)*2"); } public void GetArea() { Console.WriteLine("a*b"); } public void Draw() { Console.WriteLine("Rectangle"); } }
class Circle : IGeometrical, IDrawable { public void GetPerimeter() { Console.WriteLine("2*pi*r"); } public void GetArea() { Console.WriteLine("pi*r^2"); } public void Draw() { Console.WriteLine("Circle"); } }
Здесь был объявлен интерфейс IDrawable , который предоставляет метод для рисования объекта. Этот интерфейс может реализовать, например, класс Image .
Классы Image и Circle совсем разные сущности, и они не имеют общего базового класса, но мы можем создать список указателей на интерфейс IDrawable , и работать с такими объектами, как с однотипными (с одинаковым интерфейсом). Этот пример с IDrawable более наглядно отображает то, что нам дают интерфейсы. На практике, IGeometrical стоило бы заменить на абстрактный класс.
Операция is
При работе с объектом через объект типа интерфейса бывает необходимо убедиться, что объект поддерживает данный интерфейс.
Проверка выполняется с помощью бинарной операции is. Она определяет, совместим ли текущий тип объекта, находящегося слева от ключевого слова is, с типом, заданным справа.
Результат операции равен true, если объект можно преобразовать к заданному типу, и false в противном случае. Операция обычно используется в следующем контексте:
if ( объект is тип )
{
// выполнить преобразование "объекта" к "типу"
// выполнить действия с преобразованным объектом
}
Операция as
Операция as выполняет преобразование к заданному типу, а если это невозможно, формирует результат null:
static void Act( object A )
{
IAction Actor = A as IAction;
if ( Actor != null ) Actor.Draw();
}
Обе рассмотренные операции применяются как к интерфейсам, так и к классам.