- Автор темы
- #1
Интерфейсы.
Итак, еще одно кажущееся таким знакомым слово. Его часто можно встретить в повседневной компьютерной жизни и, где бы оно ни встретилось, толкования этого слова могут несколько отличаться друг от друга. Проясним его значение в контексте языка C#.
Прежде всего введем понятие контракта. Контракт - это набор четко определенных условий, которые регулируют отношения между классом-сервером и его клиентами; включает индивидуальные контракты для всех экспортируемых членов класса и глобальные свойства класса.
Интерфейс определяет контракт, а классы и структуры, реализующие данный интерфейс, должны четко придерживаться данного контракта. Другими словами интерфейс - это набор описаний методов и свойств, которые реализующий интерфейс класс или структура (класс иди структура наследуют данный интерфейс) обязаны определить. Таким образом, интерфейс напоминает абстрактный класс, у которого все методы абстрактны.
Использование интерфейсов - удобный способ разделения функциональных возможностей. Сначала определяются интерфейсы, а затем разрабатываются классы, реализующие данные интерфейсы. Методы класса могут быть сгруппированы в разных интерфейсах. Основной задачей интерфейса является определение контракта, не зависящего от реализации. Каждый интерфейс имеет набор методов, не реализованных непосредственно. Для каждого метода определена сигнатура, описывающая количество и тип аргументов, а также тип возвращаемого методом значения.
Пример интерфейса, описывающего методы для масштабирования объекта.
Правила описания интерфейсов
Интерфейсы могут быть объявлены с любым из четырех модификаторов доступа, описанных ранее (при условии их вложенности в другие элементы), но все элементы интерфейсов должны быть общедоступны (по умолчанию).
Интерфейсы могут быть членами пространств имен, классов или структур, и могут содержать описания следующих элементов:
Последние три элемента будут рассмотрены в последующих уроках.
*******
Интерфейсы и наследование
Вам уже известно, что в языке C# разрешено только простое наследование (наследуется только один класс). В случае с интерфейсами разрешено и простое и множественное наследование.
В случае, если класс наследует некий базовый класс и реализует один или несколько интерфейсов, в линии наследования класс ставится на первое место.
При преобразовании объекта к интерфейсу в иерархии наследования ищется класс, у которого в списке базовых классов указан этот интерфейс. Одного наличия правильных функций недостаточно.
Вывод на экран:
Проведем анализ кода и результата. Сначала создается ссылка типа базового класса T, а затем выделяется память под объект производного класса TDerived, соответственно, вызываются конструктор базового и производных классов (на экране T и TDerived).
Затем создается ссылка типа интерфейса IF, которая получает адрес объекта td, приведенного к интерфейсу IF (кстати, такое преобразование имеет смысл только в том случае, если приводимый объект реализует данный интерфейс или данный интерфейс реализует один из его предков). Затем вызывается функция F из класса T (на экране T). Почему? Ведь td ссылается на класс TDerived. Дело в том, что класс TDerived в явном виде не реализует интерфейс IF, хотя и имеет соответствующую функцию. Для того чтобы вызывалась "нужная" функция, пример необходимо переписать следующим образом:
Вывод на экран:
*******
Если некоторый интерфейс реализуется классом, возможно неявное преобразование ссылки на экземпляр класса в ссылку на интерфейс. Такое преобразование относится к неявным, поскольку на этапе компиляции заведомо известно, что оно пройдет успешно.
*******
Оператор is
Перепишем предыдущий пример следующим образом:
Результатом выполнения данного примера будет ошибка времени выполнения. В чем проблема? А она кроется в строке
Дело в том, что элемент tarr[0] ссылается на объект класса T, который не реализует интерфейс IF. Следовательно, при попытке приведения мы получим исключение типа InvalidCastException. В языке C# существует возможность проверить реализует ли данный класс данный интерфейс с помощью оператора is. Задача данного оператора проверить совместим ли тип (времени выполнения) объекта с данным типом. Перепишем пример снова:
Вывод на экран:
*******
Можно было, конечно, отловить сбой при помощи обработки исключений, но это слишком загромождает программу.
*******
Оператор as
Если вы заметили, в предыдущем примере выполняется лишнее действие, связанное с тем, что тип объекта проверяется дважды - при выполнении оператора is и при приведении типа. Поэтому в предыдущем примере разумнее воспользоваться оператором as, задача которого попытаться осуществить приведение типов без возбуждения исключительной ситуации (в случае невозможности приведения оператор возвратит null). И снова перепишем пример:
Вывод на экран:
*******
Операторы is и as могут применяться к классам.
*******
Явная реализация интерфейсов
В некоторых случаях вы можете захотеть скрыть реализацию интерфейса от пользователей класса. В этом случае можно применить технологию явной реализации интерфейсов.
Пример:
Интерфейсы, основанные на других интерфейсах
Интерфейсы можно объединять, образуя новые интерфейсы.
Данный код означает, что класс, реализующий интерфейс IC, должен будет реализовать все три функции интерфейса (f, g, и h).
Более подробную информацию можно получить, изучив практические
примеры, или заглянув в MSDN.
Итак, еще одно кажущееся таким знакомым слово. Его часто можно встретить в повседневной компьютерной жизни и, где бы оно ни встретилось, толкования этого слова могут несколько отличаться друг от друга. Проясним его значение в контексте языка C#.
Прежде всего введем понятие контракта. Контракт - это набор четко определенных условий, которые регулируют отношения между классом-сервером и его клиентами; включает индивидуальные контракты для всех экспортируемых членов класса и глобальные свойства класса.
Интерфейс определяет контракт, а классы и структуры, реализующие данный интерфейс, должны четко придерживаться данного контракта. Другими словами интерфейс - это набор описаний методов и свойств, которые реализующий интерфейс класс или структура (класс иди структура наследуют данный интерфейс) обязаны определить. Таким образом, интерфейс напоминает абстрактный класс, у которого все методы абстрактны.
Использование интерфейсов - удобный способ разделения функциональных возможностей. Сначала определяются интерфейсы, а затем разрабатываются классы, реализующие данные интерфейсы. Методы класса могут быть сгруппированы в разных интерфейсах. Основной задачей интерфейса является определение контракта, не зависящего от реализации. Каждый интерфейс имеет набор методов, не реализованных непосредственно. Для каждого метода определена сигнатура, описывающая количество и тип аргументов, а также тип возвращаемого методом значения.
Пример интерфейса, описывающего методы для масштабирования объекта.
Код:
interface IScalable
{
void ScaleX(double sx);
void ScaleY(double sy);
}
Правила описания интерфейсов
Интерфейсы могут быть объявлены с любым из четырех модификаторов доступа, описанных ранее (при условии их вложенности в другие элементы), но все элементы интерфейсов должны быть общедоступны (по умолчанию).
Интерфейсы могут быть членами пространств имен, классов или структур, и могут содержать описания следующих элементов:
- Методов
- Свойств
- Индексаторов
- Событий
Последние три элемента будут рассмотрены в последующих уроках.
*******
Интерфейсы и наследование
Вам уже известно, что в языке C# разрешено только простое наследование (наследуется только один класс). В случае с интерфейсами разрешено и простое и множественное наследование.
Код:
interface IA
{
void f();
}
interface IB
{
void g();
}
class C : IA, IB
{
// обязательная реализация функций f и g
public void f()
{
...
}
public void g()
{
...
}
}
В случае, если класс наследует некий базовый класс и реализует один или несколько интерфейсов, в линии наследования класс ставится на первое место.
Код:
interface IA
{
void f();
}
interface IB
{
void g();
}
class Base
{
...
}
class C : Base, IA, IB
{
// обязательная реализация функций f и g
public void f()
{
...
}
public void g()
{
...
}
}
При преобразовании объекта к интерфейсу в иерархии наследования ищется класс, у которого в списке базовых классов указан этот интерфейс. Одного наличия правильных функций недостаточно.
Код:
interface IF
{
void F();
}
class T : IF
{
public void F()
{
Console.WriteLine("T");
}
public T()
{
F();
}
}
class TDerived : T
{
public new void F()
{
Console.WriteLine("TDerived");
}
public TDerived()
{
F();
}
}
class Test
{
static void Main()
{
T td = new TDerived();
// Преобразование объекта к интерфейсу
IF iif = (IF)td;
iif.F();
}
}
Вывод на экран:
T
TDerived
T
Проведем анализ кода и результата. Сначала создается ссылка типа базового класса T, а затем выделяется память под объект производного класса TDerived, соответственно, вызываются конструктор базового и производных классов (на экране T и TDerived).
Затем создается ссылка типа интерфейса IF, которая получает адрес объекта td, приведенного к интерфейсу IF (кстати, такое преобразование имеет смысл только в том случае, если приводимый объект реализует данный интерфейс или данный интерфейс реализует один из его предков). Затем вызывается функция F из класса T (на экране T). Почему? Ведь td ссылается на класс TDerived. Дело в том, что класс TDerived в явном виде не реализует интерфейс IF, хотя и имеет соответствующую функцию. Для того чтобы вызывалась "нужная" функция, пример необходимо переписать следующим образом:
Код:
interface IF
{
void F();
}
class T : IF
{
public void F()
{
Console.WriteLine("T");
}
public T()
{
F();
}
}
class TDerived : T, IF
{
public new void F()
{
Console.WriteLine("TDerived");
}
public TDerived()
{
F();
}
}
class Test
{
static void Main()
{
T td = new TDerived();
// Преобразование объекта к интерфейсу
IF iif = (IF)td;
iif.F();
}
}
Вывод на экран:
T
TDerived
TDerived
*******
Если некоторый интерфейс реализуется классом, возможно неявное преобразование ссылки на экземпляр класса в ссылку на интерфейс. Такое преобразование относится к неявным, поскольку на этапе компиляции заведомо известно, что оно пройдет успешно.
*******
Код:
class Test
{
static void Main()
{
T td = new TDerived();
// Неявное преобразование объекта к интерфейсу
IF iif = td;
iif.F();
}
}
Оператор is
Перепишем предыдущий пример следующим образом:
Код:
interface IF
{
void F();
}
class T
{
public void F()
{
Console.WriteLine("T");
}
public T()
{
F();
}
}
class TDerived : T, IF
{
public new void F()
{
Console.WriteLine("TDerived");
}
public TDerived()
{
F();
}
}
class Test
{
static void Main()
{
T [] tarr = new T[4];
tarr[0] = new T();
tarr[1] = new TDerived();
tarr[2] = new T();
tarr[3] = new TDerived();
foreach(T t in tarr)
{
IF iif = (IF)t;
iif.F();
}
}
}
Результатом выполнения данного примера будет ошибка времени выполнения. В чем проблема? А она кроется в строке
IF iif = (IF)t;
Дело в том, что элемент tarr[0] ссылается на объект класса T, который не реализует интерфейс IF. Следовательно, при попытке приведения мы получим исключение типа InvalidCastException. В языке C# существует возможность проверить реализует ли данный класс данный интерфейс с помощью оператора is. Задача данного оператора проверить совместим ли тип (времени выполнения) объекта с данным типом. Перепишем пример снова:
Код:
interface IF
{
void F();
}
class T
{
public void F()
{
Console.WriteLine("T");
}
public T()
{
F();
}
}
class TDerived : T, IF
{
public new void F()
{
Console.WriteLine("TDerived");
}
public TDerived()
{
F();
}
}
class Test
{
static void Main()
{
T [] tarr = new T[4];
tarr[0] = new T();
tarr[1] = new TDerived();
tarr[2] = new T();
tarr[3] = new TDerived();
foreach(T t in tarr)
{
if(t is IF) // Проверка
{
IF iif = (IF)t;
iif.F();
}
}
}
}
Вывод на экран:
T
T
TDerived
T
T
TDerived
TDerived
TDerived
*******
Можно было, конечно, отловить сбой при помощи обработки исключений, но это слишком загромождает программу.
*******
Оператор as
Если вы заметили, в предыдущем примере выполняется лишнее действие, связанное с тем, что тип объекта проверяется дважды - при выполнении оператора is и при приведении типа. Поэтому в предыдущем примере разумнее воспользоваться оператором as, задача которого попытаться осуществить приведение типов без возбуждения исключительной ситуации (в случае невозможности приведения оператор возвратит null). И снова перепишем пример:
Код:
interface IF
{
void F();
}
class T
{
public void F()
{
Console.WriteLine("T");
}
public T()
{
F();
}
}
class TDerived : T, IF
{
public new void F()
{
Console.WriteLine("TDerived");
}
public TDerived()
{
F();
}
}
class Test
{
static void Main()
{
T [] tarr = new T[4];
tarr[0] = new T();
tarr[1] = new TDerived();
tarr[2] = new T();
tarr[3] = new TDerived();
foreach(T t in tarr)
{
IF iif = t as IF;
if(iif != null) // Проверка
{
iif.F();
}
}
}
}
Вывод на экран:
T
T
TDerived
T
T
TDerived
TDerived
TDerived
*******
Операторы is и as могут применяться к классам.
*******
Явная реализация интерфейсов
В некоторых случаях вы можете захотеть скрыть реализацию интерфейса от пользователей класса. В этом случае можно применить технологию явной реализации интерфейсов.
Пример:
Код:
interface IA
{
void f();
}
class C : IA
{
// Явная реализация функции интерфейса;
// режим доступа указывать запрещается
void IA.f()
{
...
}
}
class Test
{
static void Main()
{
C c = new C();
IA a = (IA)c;
// Вызов данной функции интерфейса возможен только
// посредством явного преобразования объекта к
// соответствующему интерфейсу
a.f();
}
}
Интерфейсы, основанные на других интерфейсах
Интерфейсы можно объединять, образуя новые интерфейсы.
Код:
interface IA
{
void f();
}
interface IB
{
void g();
}
interface IC : IA, IB
{
void h();
}
Данный код означает, что класс, реализующий интерфейс IC, должен будет реализовать все три функции интерфейса (f, g, и h).
Более подробную информацию можно получить, изучив практические
примеры, или заглянув в MSDN.