Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DiVM / OSISP / ОCиСП-Часть3 / Теория / Теория (ОСиСП).doc
Скачиваний:
29
Добавлен:
11.05.2015
Размер:
616.96 Кб
Скачать

Процедурные типы данных (делегаты)

Процедурные типы данных позволяют разработать программу с так называемыми “обратными связями”. Делегаты необходимы в программах в следующих случаях: когда необходимо предоставить возможность третьим разработчикам расширять функциональность программы без какого-либо изменения готовых модулей (без их перекомпиляции). Например, компонент Button имеет делегата (процедурный тип) – click.

Пусть необходимо связать какие-то действия с каким-то нажатием на кнопку. Делегаты позволяют запланированно вставить в код вызов неизвестного на этапе компиляции метода (т.е. адрес которого не известен на этапе компиляции программы). Делегаты позволяют организовать анонимные связи в программе, когда имя процедуры в момент вызова неизвестен:

void A( )

{

(* B) ( ); // на C++, B хранит указатель на процедуру

}

Делегат – это указатель на процедуру, но физически делегат – это два указателя:

1-ый – указатель на метод (процедуру) объекта, 2-ой – указатель на объект, чей метод хранится в первом указателе.

Пример: процедурный тип объявляется следующим образом (на Delphi)

type

ClickProc = procedure (Position:Point); // объявление процедурного типа данных

var

Click:ClickProc; // объявление перменной

type

Button = class

Click:ClickProc; // физически это поле - двойной указатель(изначально null)

end;

Form = class // класс Form

procedure ButtonClick (Position:Point); // процедура по списку совпадает с

// процедурным типом ClickProc

end;

var

B:Button;

F:Form;

begin

F:=Form.Create;

B:=Button.Create;

B.Click=F.ButtonClick; // положить в поле Click в Method адрес метода

// ButtonClick, берется адрес объекта F и кладется в поле

// Object

if Assigned (B.Click) then

B.Click (…); // обращение к полю с вызовом метода

Указатель на метод может указывать на статический метод (метод, который оперирует не объектом, а является глобальными процедурами). Если переменная процедурного типа указывает на глобальную процедуру, то в поле Object этой переменной вписывается null в качестве значения. Код вызова метода всегда имеет следующую логику: сначала проверяется, если значение подполя Object не null, то в стек дополнительно помещается указатель на объект, если = null, то в стек не помещается.

В C++ нет раздела объявления типов. Поэтому ввели раздел delegate:

public delegate void ClickProc (Point Position);

ClickProc Click = new ClickProc (F.ButtonClick); // это поле необходимо создавать

// явно

В .Net есть возможность многоадресового вызова, делегаты могут иметь много получателей (процедур, которые вызываются). Физически делегаты .Net бывают простые (как в примере с Delphi) это объект стандартного класса Delegate, а есть делегаты стандартного класса MultiCastDelegate (добавляются дополнительные поля).

MultiCastDelegate физически имеет следующую структуру:

Prev – это ссылка на предыдущего делегата, т.е. делегаты могут собираться в список. Вызов такого делегата не отличается от вызова обычного делегата, но за ним стоит вызов по списку всех процедур, которые были добавлены в этот список. Это означает, что вместо оператора присваивания значения процедурной переменной необходим оператор включения метода объекта в список делегатов.

Включение новой процедуры (нового метода):

Вновь добавленный делегат – в списке первый. Вызов делегатов по цепочке – рекурсивный. Вызывается специальная процедура, которой передается _target, _methodPtr, _prev. Это процедура вызывает себя:

void CallDelegate (void *target, void *method, void *prev)

{

if ( prev != null)

CallDelegate (prev -> target, prev -> method, prev -> prev …);

(target -> *method) (…);

}

параметры метода

Т.о., первым будет вызван самый последний (тот, кто был добавлен первым):

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

Если делегат удаляется из списка во время выполнения, то он все равно будет вызван.

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

event – это MultiCastDelegate, для которого определены операторы добавления и удаления в список, из списка делегатов (+=, -=).