Какой класс реализует базовую функциональность интерфейсных элементов
Перейти к содержимому

Какой класс реализует базовую функциональность интерфейсных элементов

  • автор:

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

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

Как и для абстрактных классов, невозможно определить объект с помощью интерфейса.

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

class имя_класса спецификация_базы

объявления_членов_класса

Спецификация базы класса в этом случае имеет вид:

:имя_базового_классаор1 , opt список_интерфейсоворt

Имя базового класса (и следующая за ним запятая) могут отсутствовать. В списке интерфейсов через запятые помещаются имена тех интерфейсов, которые должен реализовать класс. В спецификацию базы класса может входить только один базовый класс и произвольное число имен интерфейсов. При этом должно выполняться обязательное условие — класс должен реализовать все члены всех интерфейсов, включенных в спецификацию базы. Частный случай — класс, реализующий только один интерфейс:

class имя_класса: имя_интерфейса

объявления_членов_класса

Реализацией члена интерфейса является его полное определение в реализующем классе, снабженное модификатором доступа public.

Сигнатуры и типы возвращаемых значений методов, свойств и индексаторов в реализациях и в интерфейсе должны полностью совпадать.

Покажем на примерах, как при построении класса на основе интерфейса класс реализует его методы, свойства и индексаторы. Реализацию событий мы рассмотрим позднее в главе, посвященной событиям.

В следующей программе (14_01.cs) интерфейс IPublication реализуется классом Item — «заметка в газете».

// 14_01.csИнтерфейс и его реализация using System; interface IPublication < // интерфейс публикаций

void write(); // готовить публикацию

void readQ; // читать публикацию

string Title < set; get; >// название публикации

string newspaper = «Известия»; // название газеты string headline; // заголовок статьи

public void write()

/* операторы, имитирующие подготовку статьи */

public void read()

Console.WriteLine(@»npo4en в газете «»»» статью «»»».», newspaper. Title);

static void Main()

Console.WriteLine(«Publication!»); Item article = new Item(); article.Title = «О кооперации»; article.read();

Результат выполнения программы:

Прочел в газете «Известия» статью «О кооперации».

Класс Item кроме реализаций членов интерфейса включает объявления закрытых полей: newspaper (название газеты) и headline (заголовок статьи). Для простоты в класс не включен конструктор и только условно обозначены операторы методов write () и read(). Реализация свойства Title приведена полностью — аксессоры get и set позволяют получить и задать название статьи, представляемой конкретным объектом класса Item. В методе Main() нет ничего незнакомого читателю — определен объект класса Item и ссылка article на него. С помощью обращения arcticle.Title задано название статьи.

В UML для изображения интерфейсов применяется та же символика, что и для классов (см. рис. 14.1). Конечно, имеется отличие — в верхней части, после имени интерфейса IPublication помещается служебное слово Interface. Тот факт, что класс реализует интерфейс, отображается с помощью специального символа и указанием имени интерфейса над прямоугольником, представляющим класс.

Диаграмма классов с интерфейсом для программы 14_01.cs

Рис. 14.1. Диаграмма классов с интерфейсом для программы 14_01.cs

В качестве второго примера рассмотрим интерфейс, реализация членов которого позволит получать целочисленные значения членов числовых рядов [9]:

void setBeginQ; // восстановить начальное состояние int GetNext // вернуть очередной член ряда int this[int k] // вернуть к-й член ряда

Поясним роли прототипов, входящих в этот интерфейс. В приведенном объявлении: setBegin() — прототип метода; GetNext — имя свойства, позволяющего получить значение очередного члена ряда и настроить объект на следующий член. Индексатор в этом примере позволяет получить значение не очередного, а произвольного k-го члена ряда и перевести объект в состояние, при котором свойство GetNext вернет значение (к+1)-го члена.

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

Интерфейс ISeries можно реализовать для представления разных числовых рядов. Вот, например, регулярные числовые последовательности, которые можно представить классами, реализующими интерфейс ISeries:

  • 1, 1, 2, 3, 5, 8, 13, . — ряд Фибоначчи: ai=ai_2+ai_1, где i > 2; аг= 1, а2= 1;
  • 1.3.4.7.11.18.29.. ..—ряд Лукаса: at= а^+а^, где! >2; ах =1, а2= 3;
  • 1.2.5.12.29.70.. ..—ряд Пелла: а* =а,_2 + 2 * а^_х, где i > 2; а1= 1, а2= 2. В следующей программе на основе интерфейса ISeries определен

класс, представляющий ряд Пелла (см. диаграмму на рис 14.2).

// 14_02.cs — Интерфейс и его реализация using System;

interface ISeries // интерфейс числовых рядов

void setBegin(); // задать начальное состояние

int GetNext < get; >11 вернуть очередной член ряда

int this[int k] < get; >// вернуть к-й член ряда

class Pell : ISeries 11 Ряд Пелла: 1, 2, 5, 12> .

int old, last; // два предыдущих члена ряда

public Pell() < setBeginQ; >11 конструктор public void setBeginQ // задать начальное состояние

public int GetNext // вернуть следующий после Last

int now = old + 2 * last; old = last; last = now; return now;

public int this[int к] // вернуть к-й член ряда

int now = 0; setBegin();

Рис. 14.2. Диаграмма классов с интерфейсом для программы 14_02.cs

Обратите внимание, что в реализации интерфейса заголовок метода должен полностью совпадать с заголовком прототипа этого метода в интерфейсе (за исключением появления модификатора доступа). Соответствие должно быть и при реализации других членов интерфейса.

В наших примерах интерфейсы и реализующие их классы размещены в одном файле. Если интерфейс объявлен не в том файле, где выполняется его реализация, то объявление интерфейса необходимо снабдить соответствующим модификатором доступа.

В приведенных выше программах классов Item и Pell была использована неявная реализация членов интерфейсов. Термин «неявная» употребляется для обозначения того, что в объявлении класса, реализующего интерфейс, не применяются квалифицированные имена членов интерфейса и их реализации снабжаются обязательным модификатором public.

Когда один класс реализует несколько интерфейсов, возможны совпадения имен членов из разных интерфейсов. Для разрешения такой конфликтной ситуации в классе, реализующем интерфейс, используется квалифицированное имя члена интерфейса. Здесь существует ограничение — такая реализация члена называется явной и она не может быть открытой, т. е. для нее нельзя использовать модификатор public. Подробнее об особенностях явной реализации интерфейсов можно узнать, например, из работ [1, 5].

Какой класс реализует базовую функциональность интерфейсных элементов

Кроме неявного применения интерфейсов, которое было рассмотрено в прошлой статье, существует также явная реализация интерфейса. При явной реализации указывается название метода или свойства вместе с названием интерфейса. При этом при реализации мы не можем использовать какие-либо модификаторы:

interface IAction < void Move(); >class BaseAction : IAction < void IAction.Move() =>Console.WriteLine("Move in Base Class"); >

Следует учитывать, что при явной реализации интерфейса его методы и свойства не являются частью интерфейса класса. Поэтому напрямую через объект класса мы к ним обратиться не сможем:

BaseAction baseAction1 = new BaseAction(); // baseAction1.Move(); // ! Ошибка - в BaseAction нет метода Move // необходимо приведение к типу IAction // небезопасное приведение ((IAction)baseAction1).Move(); // безопасное приведение if (baseAction1 is IAction action) action.Move(); // или так IAction baseAction2 = new BaseAction(); baseAction2.Move();

В какой ситуации может действительно понадобиться явная реализация интерфейса? Например, когда класс применяет несколько интерфейсов, но они имеют один и тот же метод с одним и тем же возвращаемым результатом и одним и тем же набором параметров:

class Person : ISchool, IUniversity < public void Study() =>Console.WriteLine(«Учеба в школе или в университете»); > interface ISchool < void Study(); >interface IUniversity

Класс Person определяет один метод Study() , создавая одну общую реализацию для обоих примененных интерфейсов. И вне зависимости от того, будем ли мы рассматривать объект Person как объект типа ISchool или IUniversity, результат метода будет один и тот же.

Чтобы разграничить реализуемые интерфейсы, надо явным образом применить интерфейс:

class Person : ISchool, IUniversity < void ISchool.Study() =>Console.WriteLine("Учеба в школе"); void IUniversity.Study() => Console.WriteLine("Учеба в университете"); >
Person person = new Person(); ((ISchool)person).Study(); ((IUniversity)person).Study();

Другая ситуация, когда в базовом классе уже реализован интерфейс, но необходимо в производном классе по-своему реализовать интерфейс:

interface IAction < void Move(); >class BaseAction : IAction < public void Move() =>Console.WriteLine("Move in BaseAction"); > class HeroAction : BaseAction, IAction < void IAction.Move() =>Console.WriteLine("Move in HeroAction"); >

Несмотря на то, что базовый класс BaseAction уже реализовал интерфейс IAction, но производный класс по-своему реализует его. Применение классов:

HeroAction action1 = new HeroAction(); action1.Move(); // Move in BaseAction ((IAction)action1).Move(); // Move in HeroAction IAction action2 = new HeroAction(); action2.Move(); // Move in HeroAction

Модификаторы доступа

Члены интерфейса могут иметь разные модификаторы доступа. Если модификатор доступа не public, а какой-то другой, то при реализации метода, свойства или события интерфейса в классах и структурах мы можем применять два способа. Во-первых, можно также использовать явную реализацию интерфейса, реализовав все необходимые интерфейса без модификаторов доступа:

IMovable tom = new Person("Tom"); // подписываемся на событие tom.MoveEvent += () => Console.WriteLine($" is moving"); tom.Move(); delegate void MoveHandler(); // делегат перемещения interface IMovable < protected internal void Move(); protected internal string Name < get;>protected internal event MoveHandler MoveEvent; > class Person : IMovable < string name; // явная реализация события - дополнительно создается переменная MoveHandler? moveEvent; event MoveHandler IMovable.MoveEvent < add =>moveEvent += value; remove => moveEvent -= value; > // явная реализация свойства - в виде автосвойства string IMovable.Name < get =>name; > public Person(string name) => this.name = name; // явная реализация метода void IMovable.Move() < Console.WriteLine($"is walking"); moveEvent?.Invoke(); > >

В данном случае опять же надо учитывать, что напрямую мы можем обратиться к подобным методам, свойствам и событиям через переменную интерфейса, но не переменную класса.

Альтернативный вариант представляет неявная реализация с модификатором public

Person tom = new Person("Tom"); // подписываемся на событие tom.MoveEvent += () => Console.WriteLine($" is moving"); tom.Move(); delegate void MoveHandler(); interface IMovable < protected internal void Move(); protected internal string Name < get;>protected internal event MoveHandler MoveEvent; > class Person : IMovable < string name; // явная реализация события - дополнительно создается переменная MoveHandler? moveEvent; // неявная реализация события с модификатором public public event MoveHandler MoveEvent < add =>moveEvent += value; remove => moveEvent -= value; > // неявная реализация свойства - в виде автосвойства, но с модификатором public public string Name < get =>name; > public Person(string name) => this.name = name; // неявная реализация метода, но с модификатором public public void Move() < Console.WriteLine($"is walking"); moveEvent?.Invoke(); > >

В этом случае к методам, свойствам и событиям интейрфейса можно обращаться через переменную класса.

Какой класс реализует базовую функциональность интерфейсных элементов

Интерфейс представляет некое описание типа, набор компонентов, который должен иметь тип данных. И, собственно, мы не можем создавать объекты интерфейса напрямую с помощью конструктора, как например, в классах:

IMovable m = new IMovable(); // ! Ошибка, так сделать нельзя interface IMovable

В конечном счете интерфейс предназначен для реализации в классах и структурах. Например, реализуем выше определенный интерфейс IMovable:

// применение интерфейса в классе class Person : IMovable < public void Move() < Console.WriteLine("Человек идет"); >> // применение интерфейса в структуре struct Car : IMovable < public void Move() < Console.WriteLine("Машина едет"); >>

При применении интерфейса, как и при наследовании после имени класса или структуры указывается двоеточие и затем идут названия применяемых интерфейсов. При этом класс должен реализовать все методы и свойства применяемых интерфейсов, если эти методы и свойства не имеют реализации по умолчанию.

Если методы и свойства интерфейса не имеют модификатора доступа, то по умолчанию они являются публичными, при реализации этих методов и свойств в классе и структуре к ним можно применять только модификатор public .

Применение интерфейса в программе:

Person person = new Person(); Car car = new Car(); DoAction(person); DoAction(car); void DoAction(IMovable movable) => movable.Move(); interface IMovable < void Move(); >class Person : IMovable < public void Move() =>Console.WriteLine("Человек идет"); > struct Car : IMovable < public void Move() =>Console.WriteLine("Машина едет"); >

В данной программе определен метод DoAction() , который в качестве параметра принимает объект интерфейса IMovable. На момент написания кода мы можем не знать, что это будет за объект — какой-то класс или структура. Единственное, в чем мы можем быть уверены, что этот объект обязательно реализует метод Move и мы можем вызвать этот метод.

Иными словами, интерфейс — это контракт, что какой-то определенный тип обязательно реализует некоторый функционал.

Консольный вывод данной программы:

Человек идет Машина едет

Реализация интерфейсов по умолчанию

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

IMovable tom = new Person(); Car tesla = new Car(); tom.Move(); // Walking tesla.Move(); // Driving interface IMovable < void Move() =>Console.WriteLine("Walking"); > class Person : IMovable < >class Car : IMovable < public void Move() =>Console.WriteLine("Driving"); >

В данном случае интерфейс IMovable определяет реализацию по умолчанию для метода Move . Класс Person не реализует этот метод, поэтому он применяет реализацию по умолчанию в отличие от класса Car , который определяет свою реализацию для метода Move.

Стоит отметить, что хотя для объекта класса Person мы можем вызвать метод Move — ведь класс Person применяет интерфейс IMovable , тем не менее мы не можем написать так:

Person tom = new Person(); tom.Move(); // Ошибка - метод Move не определен в классе Person

Множественная реализация интерфейсов

Интерфейсы имеют еще одну важную функцию: в C# не поддерживается множественное наследование, то есть мы можем унаследовать класс только от одного класса, в отличие, скажем, от языка С++, где множественное наследование можно использовать. Интерфейсы позволяют частично обойти это ограничение, поскольку в C# классы и структуры могут реализовать сразу несколько интерфейсов. Все реализуемые интерфейсы указываются через запятую:

class myClass: myInterface1, myInterface2, myInterface3, .

Рассмотрим на примере:

Message hello = new Message("Hello World"); hello.Print(); // Hello World interface IMessage < string Text < get; set; >> interface IPrintable < void Print(); >class Message : IMessage, IPrintable < public string Text < get; set; >public Message(string text) => Text = text; public void Print()=> Console.WriteLine(Text); >

В данном случае определены два интерфейса. Интерфейс IMessage определяет свойство Text, которое представляет текст сообщения. А интерфейс IPrintable определяет метод Print.

Класс Message реализует оба интерфейса и затем применяется в программе.

Интерфейсы в преобразованиях типов

Все сказанное в отношении преобразования типов характерно и для интерфейсов. Поскольку класс Message реализует интерфейс IMessage, то переменная типа IMessage может хранить ссылку на объект типа Message:

// Все объекты Message являются объектами IMessage IMessage hello = new Message("Hello METANIT.COM"); Console.WriteLine(hello.Text); // Hello METANIT.COM // Не все объекты IMessage являются объектами Message, необходимо явное приведение // Message someMessage = hello; // ! Ошибка // Интерфейс IMessage не имеет свойства Print, необходимо явное приведение // hello.Print(); // ! Ошибка // если hello представляет класс Message, выполняем преобразование if (hello is Message someMessage) someMessage.Print();

Преобразование от класса к его интерфейсу, как и преобразование от производного типа к базовому, выполняется автоматически. Так как любой объект Message реализует интерфейс IMessage.

Обратное преобразование — от интерфейса к реализующему его классу будет аналогично преобразованию от базового класса к производному. Так как не каждый объект IMessage является объектом Message (ведь интерфейс IMessage могут реализовать и другие классы), то для подобного преобразования необходима операция приведения типов. И если мы хотим обратиться к методам класса Message, которые не определены в интерфейсе IMessage, но являются частью класса Message, то нам надо явным образом выполнить преобразование типов:

if (hello is Message someMessage) someMessage.Print();

Интерфейсы

Интерфейс определяет ряд методов (свойств, индексаторов, событий), которые должны быть реализованы в классе, который наследует (реализует) данный интерфейс. Интерфейсы используются для того, чтобы указать классам, что именно нужно реализовать в этих классах. Реализовывать нужно методы (свойства, индексаторы, события). Таким образом, интерфейс описывает функциональные возможности без конкретной реализации. Иными словами интерфейс определяет спецификацию но не реализацию.

Использование интерфейсов есть эффективным в случаях, когда нужно создать альтернативу множественного наследования. Любой класс может унаследовать несколько интерфейсов. При этом все методы унаследованных интерфейсов должны быть реализованы в классе.

Структура также как и класс может реализовывать любое количество интерфейсов.

Особенности интерфейсов

  • в интерфейсе нельзя вписывать реализацию его элементов;
  • невозможно создать экземпляр интерфейса;
  • можно создать ссылку на интерфейс;
  • в интерфейсе не может быть конструкторов;
  • интерфейс не может содержать поля;
  • в интерфейсе не может быть осуществлена перегрузка операторов;
  • все методы интерфейса по умолчанию объявлены как public .

При использовании интерфейсов в классах-наследниках:

  • запрещено изменять модификатор доступа для метода при его реализации;
  • невозможно объявить методы интерфейса как virtual ;
  • запрещено объявлять методы интерфейса с ключевым словом static (как статические).

2. Какое отличие между интерфейсами и абстрактными классами?

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

  1. В интерфейсе запрещено прописывать реализацию его членов. В абстрактном классе часть членов может иметь реализацию. Иными словами, интерфейс это тот же абстрактный класс, у которого все методы абстрактные.
  2. В интерфейсе запрещено описывать поля (переменные, объекты), в абстрактном классе можно.
  3. В интерфейсе запрещено объявление перегруженных операторов. В абстрактном классе при соблюдении некоторых условий допускается объявление перегруженных операторов.
  4. Интерфейс не может содержать конструктор. В абстрактном классе может быть объявлен конструктор.
  5. Любой класс может быть унаследован от нескольких интерфейсов. При этом любой класс может быть унаследован только от одного абстрактного класса (и не более).
  6. В интерфейсе запрещено объявлять статические элементы (с ключевым словом static ). В абстрактном классе допускается объявление статических элементов.
  7. В интерфейсе все элементы считаются как public , и поэтому модификаторы доступа в интерфейсе не используются. В абстрактном классе элементы могут быть объявлены с любым модификатором доступа ( private , protected , public ).

3. Сколько классов могут иметь реализацию методов интерфейса?

Если интерфейс определен, то он может быть реализован в любом количестве классов.

4. Сколько интерфейсов может быть реализовано в одном классе?

В одном классе может быть реализовано любое количество интерфейсов.

5. Какой общий вид описания интерфейса?

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

interface имя  возвращаемый_тип1 имя_метода1(параметры1); возвращаемый_тип2 имя_метода2(параметры2); // .  возвращаемый_типN имя_методаN(параметрыN); >
  • имя – конкретное имя интерфейса;
  • имя_метода1 , имя_метода2 , …, имя_методаN – имена методов интерфейсов;
  • возвращаемый_тип1 , возвращаемый_тип2 , …, возвращаемый_типN – типы, которые возвращаются методами интерфейса;
  • параметры1 , параметры2 , …, параметрыN – списки параметров методов интерфейса.

Кроме методов, в интерфейсах можно указывать свойства, события и индексаторы.

6. Какие элементы языка программирования можно указывать в интерфейсах?

В интерфейсах можно указывать:

  • методы;
  • свойства;
  • индексаторы;
  • события.

7. Как выглядит общая форма реализации интерфейса в классе?

Общая форма реализации интерфейса в классе имеет следующий вид:

class имя_класса : имя_интерфейса  // тело класса . >

где имя_интерфейса – имя интерфейса, методы (свойства, индексаторы, события) которого реализуются в классе. Класс обязательно должен реализовать все методы интерфейса.

8. Какая общая форма класса реализующего несколько интерфейсов?

Класс может реализовать несколько интерфейсов. В этом случае все интерфейсы определяются списком через запятую.

Общая форма класса реализующего несколько интерфейсов:

class имя_класса : имя_интерфейса1, имя_интерфейса2, . имя_интерфейсаN  // тело класса . >

где имя_интерфейса1 , имя_интерфейса2 , …, имя_интерфейсаN – имена интерфейсов, которые должен реализовать класс. Класс должен реализовать все методы всех интерфейсов.

9. Пример объявления интерфейса и класса наследующего этот интерфейс

В данном примере интерфейсу присваивается имя IMyInterface . Рекомендовано к имени интерфейса добавить префикс ‘ I ’ в соответствии с общераспространенной практикой.

Интерфейс объявлен как public .

public interface IMyInterface  int MyGetInt(); // метод, возвращающий число типа int double MyGetPi(); // метод, возвращающий число Pi int MySquare(int x); // метод, возвращающий x в квадрате double MySqrt(double x); // метод возвращающий корень квадратный из x >

В данном примере, в интерфейсе объявлено описание четырех методов, которые должны быть реализованы во всех классах, определяющих эти интерфейсы. Это методы: MyGetInt() , MyGetPi() , MySquare() , MySqrt() .

Пример описания класса использующего этот интерфейс.

public class MyClass : IMyInterface  // модификатор доступа public public int MyGetInt()  return 25; >  public double MyGetPi()  return Math.PI; >  public int MySquare(int x)  return (int)(x * x); >  public double MySqrt(double x)  return (double)Math.Sqrt(x); > >

Все методы, которые определяются в классе, должны иметь тип доступа public . Если установить другой тип доступа ( private или protected ), то Visual Studio выдаст следующее сообщение:

"MyClass does not implement interface member MyFun() because it is not public."

где MyFun() – название функции, которая реализована в классе с модификатором доступа private или protected .

Это связано с тем, что в самом интерфейсе эти методы неявно считаются открытыми ( public ). Поэтому их реализация должна быть открытой.

10. Пример объявления двух интерфейсов и класса, который реализует методы этих интерфейсов

В нижеследующем примере объявлено два интерфейса с именами MyInterface и MyInterface2. Первый интерфейс содержит 4 методы. Второй интерфейс содержит 1 метод.

Также объявлен класс MyClass, использующий эти два интерфейса. Класс обязательно должен реализовать все методы обоих интерфейсов, то есть в сумме 5 методов.

public interface IMyInterface  int MyGetInt(); // метод, возвращающий число типа int double MyGetPi(); // метод, возвращающий число Pi int MySquare(int x); // метод, возвращающий x в квадрате double MySqrt(double x); // метод, возвращающий корень квадратный из x > public interface IMyInterface2  double MySqrt2(double x); // корень квадратный из x > public class MyClass : IMyInterface, IMyInterface2  // методы из интерфейса MyInterface public int MyGetInt()  return 25; >  public double MyGetPi()  return Math.PI; >  public int MySquare(int x)  return (int)(x * x); >  public double MySqrt(double x)  return (double)Math.Sqrt(x); >  // метод из интерфейса MyInterface2 public double MySqrt2(double x)  return (double)Math.Sqrt(x); > >

11. Пример использования ссылки на интерфейс для доступа к методам класса

В C# допускается описывать ссылки на интерфейс. Если описать переменную-ссылку на интерфейс, то с ее помощью можно вызвать методы класса, который использует этот интерфейс.

Пример.

public interface IMyInterface  double MyGetPi(); // метод, возвращающий число Pi > class MyClass : IMyInterface  // методы из интерфейса MyInterface public double MyGetPi()  return Math.PI; > > // вызов из программного кода private void button1_Click(object sender, EventArgs e)  MyClass mc = new MyClass(); // создание объекта класса mc IMyInterface mi; // ссылка на интерфейс double d; mi = mc; // mi ссылается на объект класса mc d = mi.MyGetPi(); // d = 3.14159265358979 label1.Text = d.ToString(); >

В данном примере создается объект (экземпляр) класса MyClass с именем mc . Затем описывается ссылка на интерфейс IMyInterface с именем mi .

mi=mc;

приводит к тому, что ссылка mi указывает на объект класса mc . Таким образом, через ссылку mi можно иметь доступ к методам класса MyClass , так как класс MyClass реализует методы интерфейса IMyInterface.

С помощью ссылки на интерфейс можно иметь доступ к методам классов, которые реализуют описанные в этом интерфейсе методы.

12. Каким образом в интерфейсе описывается свойство?

Свойство описывается в интерфейсе без тела. Общая форма объявления интерфейсного свойства следующая:

тип имя_свойства   get;  set; >

Если свойство предназначено только для чтения, то используется один только аксессор get .

Если свойство предназначено для записи, то используется только один аксессор set .

Пример. Описывается интерфейс и класс. Класс возвращает свойство MyPi .

public interface IMyInterface  double MyGetPi(); // метод, возвращающий число Pi  // свойство, возвращающее число Pi double MyPi  get; > > class MyClass : IMyInterface  // метод public double MyGetPi()  return Math.PI; >  // реализация свойства в классе public double MyPi  get  return Math.PI; > > > // использование интерфейсного свойства в обработчике события клика на кнопке private void button1_Click(object sender, EventArgs e)   MyClass mc = new MyClass(); // создание объекта класса mc label1.Text = mc.MyPi.ToString(); // чтение свойства >

13. Пример интерфейса, в котором описывается индексатор.

Общая форма объявления интерфейсного индексатора имеет вид:

тип this[int индекс]   get;  set; >

Пример описания и использования интерфейсного индексатора, который считывает элемент из массива, состоящего из 5 элементов типа double .

public interface IMyInterface  // интерфейсный индексатор double this[int index]  get; > > class MyClass : IMyInterface  double[] mas = < 3, 2.9, 0.5, 7, 8.3 >; public double this[int index]  get  return mas[index]; > > > private void button1_Click(object sender, EventArgs e)  MyClass mc = new MyClass(); // создание объекта класса mc double d; d = mc[2]; // d = 0.5 label1.Text = d.ToString(); >

14. Какие элементы программирования языка C# нельзя описывать в интерфейсах?

Интерфейсы не могут содержать:

  • члены данных;
  • конструкторы;
  • деструкторы;
  • операторные методы.

15. Как работает механизм наследования интерфейсов?

Интерфейс может наследовать другой интерфейс. Синтаксис наследования интерфейсов такой же, как и у классов.

Общая форма наследования интерфейса следующая:

interface имя_интерфейса : имя_интерфейса1, имя_интерфейса2, . имя_интерфейсаN  // методы, свойства, индексаторы и события интерфейса . >

где имя_интерфейса – имя интерфейса, который наследует другие интерфейсы;

имя_интерфейса1 , имя_интерфейса2 , …, имя_интерфейсаN – имена интерфейсов-предков.

Пример. В данном примере класс MyClass использует интерфейс, который наследует другой интерфейс. В классе нужно реализовать все методы (свойства, индексаторы, события) интерфейса MyInterface1 и интерфейса MyInterface2.

// базовый интерфейс interface MyInterface1  void Int1_Meth(); > // интерфейс, который наследует другой интерфейс interface MyInterface2 : MyInterface1  void Int2_Meth(); > // класс, который использует интерфейс MyInterface2 class MyClass : MyInterface2  // реализация метода интерфейса MyInterface1 public void Int1_Meth()  // тело метода // .  return; >  // реализация метода интерфейса MyInterface2 public void Int2_Meth()  // тело метода // .  return; > >

16. Что такое явная реализация члена интерфейса?

Если перед именем метода (свойства, индексатора, события) стоит имя интерфейса через разделитель ‘ . ‘ (точка), то это называется явной реализацией члена интерфейса.

Пример явной реализации.

// базовый интерфейс interface MyInterface1  void Method(); > // класс, который реализует интерфейс MyInterface1 class MyClass : MyInterface1  // явная реализация метода интерфейса MyInterface1 void MyInterface1.Method() // указывается имя интерфейса  // тело метода // .  return; > >

17. Когда целесообразно применять явную реализацию члена интерфейса? Примеры.

Явная реализация члена интерфейса применяется в следующих случаях:

  • когда нужно, чтобы интерфейсный метод был доступен по интерфейсной ссылке, а не по объекту класса, реализующего данный интерфейс. В этом случае интерфейсный метод не является открытым ( public ) членом класса (см. пример 1);
  • когда в одном классе реализованы два интерфейса, в которых методы имеют одинаковые имена и сигнатуру (см. пример 2).

Пример 1. Явная реализация интерфейсного метода. По интерфейсной ссылке метод есть доступен, а по объекту класса недоступен.

// Интерфейс interface MyInterface1  void Method(); > // класс, вызывающий интерфейс class MyClass : MyInterface1  // явная реализация метода интерфейса MyInterface1 // модификатор доступа, должен отсутствовать void MyInterface1.Method() // указывается имя интерфейса  // тело метода // .  return; >  void InternalMethod()  MyInterface1 mi = this; // mi - интерфейсная ссылка mi.Method(); // работает!  MyClass mc = this; // mc - объект класса MyClass // mc.Method() - невозможно вызвать - метод не открыт для объекта > >

Пример 2. Есть два интерфейса MyInterface1 и MyInterface2 . Каждый из них имеет методы с одинаковыми именами и сигнатурами. В данном случае это метод Method() , не возвращающий параметров ( void ). С помощью явной реализации класс распознает эти методы.

// интерфейс 1 interface MyInterface1  void Method(); > // интерфейс 2 interface MyInterface2   void Method(); > // класс, который использует два интерфейса class MyClass : MyInterface1, MyInterface2  // явная реализация - модификатор доступа (public) должен отсутствовать // метод из интерфейса MyInterface1 void MyInterface1.Method()  // тело метода // .  return; > // метод из интерфейса MyInterface2 void MyInterface2.Method()  // тело метода // . return; > >

18. В каких случаях лучше использовать интерфейс, а в каких абстрактный класс?

Интерфейс целесообразно использовать в случаях, если некоторые понятия должны быть описаны с точки зрения функционального назначения, без уточнения деталей реализации.

Абстрактный класс целесообразно использовать тогда, когда все же нужно уточнять некоторые детали реализации.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *