Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЯиМП.doc
Скачиваний:
4
Добавлен:
29.07.2019
Размер:
298.5 Кб
Скачать

Интерфейсы. Делегаты. События

Лекция 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.