Интерфейсы. Делегаты. События
Лекция 7
Свойства
В C++/CLI появился новый тип данных - свойства.
Свойство, описанное в классе, позволяет безопасно использовать связанную с ним закрытую обычную или ссылочную переменную, устанавливая или получая её значение. Свойство приписывает этой переменной имя свойства. В дальнейшем ссылка к переменной осуществляется через имя свойства.
При использовании свойства автоматически выполняются функции set и get, которые устанавливают или получают значение переменной свойства, позволяя при этом осуществлять её контроль или выполнять с ней какие-либо действия. Эти функции определяются при описании свойства.
Отличие свойства от поля состоит в том, что свойство определяет методы получения и установки своего значения.
Создание свойства
property тип имя-свойства
{
void set (тип value)
{ код реализации set, использующий параметр
value и переменную или ссылку свойства
}
тип get ()
{ код реализации get, возвращающий значение
переменной или ссылки свойства
}
}
При необходимости можно описать свойство только с одной функцией. Свойство должно иметь доступ public, иначе оно не применимо к объекту.
Можно использовать сокращенный синтаксис:
property тип имя-свойства;
Пример создания и использования свойства
ref class CPlane{ // Класс, содержащий описание свойства
private:
int aSpeed; // Закрытая переменная свойства Speed
public:
// Описание свойства Speed
property int Speed {
void set (int vProp) // Установить значение свойства
{aSpeed= vProp;}
int get() // Возвратить значение свойства
{return aSpeed;}
}
};
void main ( ){
CPlane ^p= gcnew CPlane; // Создать объект
p -> Speed= 600; // Присвоить значение
Console::WriteLine (p -> Speed); // Получить значение и выдать
Понятие интерфейса
Интерфейс – «крайний» случай абстрактного класса. В нем задается набор абстрактных методов, свойств и событий, которые должны быть реализованы в производных классах.
Синтаксис интерфейса аналогичен синтаксису класса:
interface class имя-инт [ : public
имя-базов-инт [ , public имя-базов-инт ] ... ]
{
объявление функций , свойств и событий
};
Интерфейс не может содержать конструкторы, деструкторы, поля.
Пример создания интерфейса
interface class Iprim {
property int P;
void M();
};
ref class А: public Iprim {
private: int x;
public: virtual void M( )
{Console::WriteLine ("Метод работает");}
property int P {
virtual void set (int V) {x= V;}
virtual int get ( ) {return x;}
}
};
void main ( ){
А^ pA = gcnew А(); // Создать объект класса А
pA->P = 77; // Установить свойство P
Console::WriteLine ( pA -> P.ToString ());
pA->M(); // Вызвать метод
}
Интерфейсы и абстрактные классы
Если набор действий нужно реализовать разными способами для некоторой иерархии классов, то лучше задать этот набор в виде виртуальных методов абстрактного базового класса иерархии.
То, что работает в пределах иерархии одинакового, следует полностью определить в базовом классе.
Интерфейсы обычно используются для задания общих свойств объектов разных иерархий.
Делегаты
Делегат – это класс, объект которого сохраняет, а затем вызывает одну или несколько функций с одинаковой сигнатурой.
Как любой класс, делегат – это тип данных. Чтобы воспользоваться делегатом, нужно создать его экземпляр и добавить в него необходимые функции. Вызов объекта делегата приводит к выполнению инкапсулированных в нём функций. Можно передать объект делегата в качестве параметра функции.
Делегаты используются для поддержки событий, а также как самостоятельная конструкция языка.
Описание делегата
Описание делегата определяет сигнатуру функций, которые могут быть вызваны с его помощью:
delegate тип имя ([параметры])
где:
тип – тип возвращаемого значения делегируемой функции;
имя – имя класса делегата;
параметры - список типов параметров делегируемой функции.
Это описание компилятор преобразует в класс, у которого есть конструктор и 3 метода.
Пример описания делегата:
delegate void Del(String ^name);
Такое объявление и реализация делегата предполагает делегирование функции любого имени, но точно с заданными списком параметров и типом возвращаемого значения.
Создание объекта делегата
Перед применением делегата необходимо объявить дескриптор на него, а затем с помощью оператора gcnew создать объект делегата.
Мы можем добавить в делегат обычную глобальную функцию, а также открытый метод управляемого класса (перед описанием класса стоит ключевое слово ref).
Синтаксис зависит от типа функции:
// глобальная функция MyFunction
Del ^ X = gcnew Del(&MyFunction);
// статический метод StMetod класса MyClass
Del ^ X = gcnew Del (&MyClass::StMetod);
// нестатический метод MyMetod класса MyClass
MyClass ^ C = gcnew MyClass();
Del ^ X = gcnew Del(C, &MyClass::MyMetod);
Добавление функций
Для добавления к объекту новых функций используется перегруженный оператор «+», для удаления – «-».
Синтаксис снова зависит от типа функции:
// глобальная функция MyFunction1
X = X+ gcnew Del(&MyFunction1);
X += gcnew Del(&MyFunction1);
X = X- gcnew Del(&MyFunction1);
X -= gcnew Del(&MyFunction1);
// статический метод StMetod1 класса MyClass
X += gcnew Del (&MyClass::StMetod1);
X -= gcnew Del (&MyClass::StMetod1);
// нестатический метод MyMetod1 класса MyClass
MyClass ^ C = gcnew MyClass();
X += gcnew Del(C, &MyClass::MyMetod1);
X += gcnew Del(C, &MyClass::MyMetod2);
X -= gcnew Del(C, &MyClass::MyMetod1);
Вызов функций с помощью делегата
Вызов последовательности функций с помощью делегата выглядит, как обычное обращение к функции:
X(«Пример");
При вызове нужно учитывать, что:
сигнатура функций должна точно соответствовать делегату;
функции выполняются в том порядке, в котором они добавлялись;
каждому методу передается один набор параметров;
если параметр передается по ссылке, изменения параметра в одном методе отразятся на его значении при вызове следующего метода.
Пример использования делегата как параметра функции
#include "stdafx.h"
using namespace System;
delegate void del (void); // Объявление делегата del
ref class MyCl { // Класс с функцией R() для делегата del
public:
void R(){Console::WriteLine ("R");}
};
void M (del ^d) {d( );} // Глобальная функция для делегата del
void main () {
MyCl ^ P= gcnew MyCl ( ); //Создать объект класса MyCl
del ^pDel = gcnew del(P,&MyCl::R); //Создать объект делегата
M (pDel); // Функция M() вызовет функцию MyCl::R
}
События
События – это элемент класса, позволяющий ему посылать другим объектам уведомления об изменении своего состояния. При этом для объектов, являющихся наблюдателями события, активизируются методы-обработчики этого события. Обработчики должны быть зарегистрированы в объекте-источнике события.
Применительно к событиям введена следующая терминология. Объект, генерирующий (firing) события, называют источником (source), а объект, получающий событие, называют приёмником (sink). Функции приёмника, реагирующие на событие, называют обработчиками (handlers) события.
Часто используются и другие термины. Объект, генерирующий события, называют издателем (publisher). Говорят, что издатель публикует события, на которые должны подписаться подписчики (subscribers) - объекты, обработчики которых реагируют на события издателя. Подписка на событие заключается в привязке к событию требуемого обработчика.
Создание события
События построены на основе делегатов: с помощью делегатов вызываются методы-обработчики событий.
Создание события включает следующие шаги:
описание делегата, задающего сигнатуру обработчиков событий;
описание события;
описание метода (методов), инициирующих событие.
Например:
delegate void Del (); // Делегат события
ref class GenEv { // Класс объекта-источника события
public: event Del^ pEv; //Дескриптор события
// Генерировать событие !
void GenerateEv( ) {pEv ( ) ; }
};
Обработка событий
Обработка событий выполняется в классах-получателях. Для этого в них описываются методы-обработчики событий, сигнатура которых соответствует типу делегата :
ref class UseEv { // Класс объекта-приёмника события
public: static void HandlerEv ( ) // Функция-обработчик {Console::WriteLine ("Объект получил событие");}
};
Каждый объект (не класс!), желающий получить сообщение, должен зарегистрировать в объекте-отправителе этот метод:
GenEv ^pGenEv= gcnew GenEv; // источник события
UseEv ^pUseEv= gcnew UseEv; // приёмник события
// Добавить обработчик
pGenEv -> pEv += gcnew Del (UseEv::HandlerEv);
Пример использования события
delegate void Del (); // Делегат события
ref class GenEv { // Класс объекта-источника события
public: event Del^ pEv; //Дескриптор события
void GenerateEv( ) {pEv ( ) ; } // Генерировать событие !
};
ref class UseEv { // Класс объекта-приёмника события
public: static void HandlerEv ( ) // Функция-обработчик события
{Console::WriteLine ("Объект получил событие");}
};
void main ( ){
GenEv ^pGenEv= gcnew GenEv; // источник события
UseEv ^pUseEv= gcnew UseEv; // приёмник события
// Добавить обработчик
pGenEv -> pEv += gcnew Del (UseEv::HandlerEv);
pGenEv ->GenerateEv(); // Сгенерировать событие
}
Правила оформления событий в среде .NET
При программировании событий рекомендуется:
в имени обработчика события использовать слово Handler; обработчики событий должны иметь возвращаемое значение void;
делегат события и, конечно, обработчик должен иметь следующий список параметров:
(Object ^sender, EventArgs ^args)
где sender (^sender) - дескриптор на объект-источник события,
args (^args) - дескриптор на объект класса, порождённого из класса EventArgs, который содержит открытые (public) данные или свойства, связанные с событием.
Класс EventArgs наследует класс Object и содержит единственное свойство Empty, значение false которого указывает о наличии данных, а true – об их отсутствии.
Если делегат не использует информацию о событии, то можно не описывать делегата и собственный тип аргументов, а обойтись стандартным классом делегата System.EventHandler.