Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Podbelsky_V_V_C_Bazovy_kurs.pdf
Скачиваний:
69
Добавлен:
02.06.2015
Размер:
1.73 Mб
Скачать

360

Г л а в а 1 7

 

 

0 0 0

0 1 0

1 0 1

1 1 0

17.6. События

Событие – средство, позволяющее объекту (или классу) послать во «внешний для объекта или класса мир» сообщение о переходе в некоторое новое состояние или о получении сообщения из внешнего источника. Так как на уровне программы все действия объектов и классов реализуются с помощью методов, то и посылка сообщения оформляется как оператор в теле некоторого метода. Синтаксически оператор посылки сообщения выглядит так:

имя_события (аргументы_для_делегата);

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

Отметим, что объявление события может размещаться в классе и в интерфейсе. Начнём с классов. Событие – это член класса (или его объекта), вводимый объявлением:

модификаторыopt event имя_делегата имя_события;

модификатором может быть abstract, new, override, static, virtual, public, protected, private, internal.

event – служебное слово декларации события.

имя_события – идентификатор, выбираемый программистом в качестве названия конкретного члена, называемого переменной события. В практике программирования на C# принято начинать имена событий с префикса on.

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

Таким образом, событие это член класса, имеющий тип делегата. Этот делегат-тип должен быть доступен в точке объявления события. Например, его определение должно быть в том же файле. Событийный делегат-тип определяет для события

Делегаты и события

361

 

 

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

всобытийном делегате обычно используется void.

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

delegate void ТimeHandler(); // Объявление делегата-

типа

static event ТimeHandler onTime; // Объявление события

Рекомендуется в название событийного делегата включать в качестве суффикса слово Handler (обработчик). Делегат ТimeHandler в соответствии с его объявлением предназначен “представлять” методы без параметров, с возвращаемым значением типа void (ничего не возвращающие в точку вызова). Событие с именем onTime “настроено” на работу с экземплярами делегата ТimeHandler.

В том же классе, где размещено объявление события и доступен делегат-тип, можно определить метод, через каждую секунду “генерирующий” посылку сообщений:

static void run() { // Процесс с событиями

Console.WriteLine("Для выхода нажмите Ctrl+C!"); while (true) { // Бесконечный цикл

onTime(); // Посылка сообщения о событии

System.Threading.Thread.Sleep(1000); // задержка на 1 секунду

}

}

В методе run() бесконечный цикл, в каждой итерации которого оператор onTime() посылает сообщение о событии, связанном с делегатом TimeHandler. Затем вызывается статический метод Sleep() класса Thread из пространства имен System.

362

Г л а в а 1 7

 

 

Threading. Назначение этого метода состоит в “задержке” процесса выполнения программы на количество миллисекунд, соответствующее значению аргумента. В данном случае задержка равна 1000 миллисекундам, т. е. одной секунде.

Метод run(), посылая сообщения о событиях, “ничего не знает” о том, кто будет получать эти сообщения, и как они будут обрабатываться. В технологии Windows-программирования принято говорить, что объект (в нашем примере не объект, а статический метод класса) публикует события, посылая сообщения о них. Другие объекты (в нашем примере это будут статические методы) могут подписаться на события.

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

●●создание экземпляра того делегата, на который настроено событие;

●●подключение экземпляра делегата к событию.

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

имя_события += new имя_делегата (имя_метода);

Условие применимости подписки на событие – наличие и доступность метода, который будет вызван для обработки события. Имя этого метода используется в качестве аргумента конструктора делегата. Само собой, и делегат события, и имя события должны быть доступны в месте подписки.

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

Предположим, что «принимать сообщения» о событиях в методе run() должен метод Main() того же класса, в котором определен метод run(). Пусть обработчиками сообщения о событии должны быть два метода с заголовками:

static void one() static void two()

Тогда метод Main может быть таким (программа 17_08.cs):

static void Main() {

onTime += new TimeHandler(one); // Подписка на событие

Делегаты и события

363

 

 

onTime += new TimeHandler(two); // Для метода two run(); // Запуск процесса

}

Остальное, т. е. функциональность программы в целом, зависит от возможностей и особенностей методов one() и two(). В следующей ниже программе метод one() выводит в консольное окно дату и посекундно изменяющееся значение времени. Метод two() в начале той же строки выводит порядковый номер события. Номер изменяется каждую секунду (при каждом обращении к методу).

// 17_08.cs статические события и статические

методы using System;

delegate void TimeHandler(); // Объявление делегата-

типа

class test_cs {

static event TimeHandler onTime;// Объявление события static void run() // Процесс с генерацией событий

{

Console.WriteLine("Для выхода нажмите Ctrl+C!"); while (true) { // Бесконечный цикл

onTime(); // Посылка сообщения о событии

System.Threading.Thread.Sleep(1000); // Задержка на

1 сек.

}

}

static void Main() {

onTime += new TimeHandler(one); // Подписка на событие...

onTime += new TimeHandler(two); // ...для метода two run(); // Запуск процесса

}

static void one() { // Приемник сообщения string newTime = DateTime.Now.ToString(); Console.Write("\r\t\t{0}", newTime);

}

static int count = 0;

static void two() { // Приемник сообщения Console.Write("\r{0}", count++);

}

}

364

Г л а в а 1 7

 

 

Результат выполнения программы на 7-й секунде:

Для выхода нажмите Ctrl+C!

716.05.2009 10:27:17

В тексте программы обратим внимание на вывод предупреждения пользователю:

Console.WriteLine("Для выхода нажмите Ctrl+C!");

Сочетание клавиш Ctrl и C приводит к незамедлительному прекращению выполнения программы. Чтобы не “затемнять” основную схему программы, иллюстрирующей только механизм работы с событиями, в нее не введены никакие средства диалога

спользователем.

Встроке-аргументе метода консольного вывода Write() управляющая эскейп-последовательность \r обеспечивает при каждом обращении переход в начало строки дисплея. Тем самым вывод все время выполняется в одну и ту же строку, изображение на которой обновляется ежесекундно.

Вметоде one() используется свойство Now класса Data.Time. Его назначение – вернуть текущее значение даты и времени. Применение метода ToString() позволяет представить эти значения в виде одной строки, которая затем выводится на дисплей.

Для подсчета событий (секунд) определена статическая переменная int count. Ее значение выводит и затем увеличивает на 1 метод two().

Врассмотренном примере делегат объявлен вне классов и все методы статические – генерацию событий выполняет статический метод run(), подписаны на события два других статических метода. Таким образом, с помощью механизма событий взаимодействуют не объекты, а методы одного класса test_cs. Кроме того, в объявлении делегата отсутствуют параметры. Поэтому при посылке сообщения о событии методы обработки не получают никакой информации из точки возникновения события.

Более общий случай – событие создается объектом, а в других объектах (в объектах других классов) имеется возможность реагировать на эти события. Как мы уже показали, к одному событию может быть “приписано” несколько обработчиков, и все они будут вызваны при наступлении события.

Делегаты и события

365

 

 

Механизм работы с событиями предусматривает несколько этапов.

1.Объявление делегата-типа, задающего сигнатуру тех (еще неизвестных на данном этапе) методов, которые будут вызываться при обработке события;

2.Определение переменной события, имеющей тип делегата события;

3.Определения генератора события (посылки сообщения),

суказанием аргументов, информирующих получателей о состоянии объекта, пославшего сообщение;

4.Определение методов обработки события. Сигнатура каждого метода должна соответствовать типу делегата события;

5.Создание экземпляра того делегата, на который “настроено” событие. Аргумент конструктора – имя метода обработки;

6.Подключение экземпляра делегата к переменной события.

Перечисленные этапы обычно относятся к разным классам программ. И этих разных классов по меньшей мере два. Класс, обрабатывающий события, должен содержать методы обработки или, по крайней мере, иметь доступ к этим методам. В нем реализуются этапы 4, 5, 6.

Второй класс – это класс, генерирующий события, реализует этапы 1, 2, 3.

Зачастую в программе присутствует третий класс, управляющий процессом на более высоком уровне. В разных задачах его называют супервизором, монитором, диспетчером и т.п. При наличии такого класса и двух подчиненных – класса генерации и класса обработки событий - схема работы супервизора сводится к следующим шагам.

1.Создать объект класса генерации событий;

2.Создать объект класса обработки событий (этого может не потребоваться, если метод обработки является статическим);

3.Создать экземпляр делегата, “настроив” его на метод класса обработки событий;

4.Подключить экземпляр делегата к переменной события из объекта класса генерации;

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

366

Г л а в а 1 7

 

 

Далее все выполняется в соответствии с общими принципами событийного управления.

В качестве примера рассмотрим программу с делегатом и четырьмя классами. Класс Sorting содержит метод, который сортирует в порядке возрастания одномерный целочисленный массив. В процессе сортировки подсчитывается количество перестановок значений элементов. При каждом завершении внутреннего цикла формируется событие, передающее “во внешний мир” количество выполненных перестановок, размер массива и счетчик итераций внешнего цикла. Для работы с событиями в классе объявлено событие onSort, имеющее тип внешнего делегата SortHandler.

Класс View содержит метод обработки события. Метод выводит на консоль значение счетчика перестановок.

Класс display визуализирует динамику процесса сортировки

– выводит на консоль имитацию элемента управления ProgressBar.

Метод Main() управляющего класса Controller в соответствии с общей схемой создает объект класса, генерирующего события, объект класса – обработчика (View). Затем подключает к переменной события два наблюдателя – два безымянных экземпляра делегата SortHandler. И, наконец, управление передается методу сортировки объекта – генератора событий.

//17_09.cs события и сортировка using System;

using System.Text;

//Объявление делегата-типа:

public delegate void SortHandler(long cn, int si, int kl); class Sorting // Класс сортировки массивов

{

int size;

// Размер массива

int[] ar;

// Ссылка на массив

public long count; // Счетчик обменов при сортировке public event SortHandler onSort; // Объявление события public Sorting(int[] ls) // Конструктор

{

size = ls.Length; count = 0;

ar = ls;

Делегаты и события

367

 

 

}

public void sort() // Сортировка с посылкой извещений

{

int temp;

for (int i = 0; i < size - 1; i++)

{

for (int j = i + 1; j < size; j++) if (ar[i] > ar[j])

{

temp = ar[i]; ar[i] = ar[j]; ar[j] = temp; count++;

}

if (onSort != null)

onSort(count, size, i); // Генерация события

}

}

}

class View

{ // Обработчик событий в объектах: public void nShow(long n, int si, int kl)

{

Console.Write("\r" + n);

}

}

class Display // Обработчик событий в этом классе

{

static int len=30; static string st = null;

public static void barShow(long n, int si, int kl)

{

int pos = Math.Abs((int)((double)kl / si * len)); string s1 = new string('\u258c', pos);

string s2 = new string('-', len - pos-1) + '\u25c4'; // unicode для треугольника;

st = s1 + '\u258c' + s2; //'\u258c' - код прямоугольника

Console.Write("\r\t\t" + st);

}

}

class Controller

368

Г л а в а 1 7

 

 

{

static void Main()

{

Random ran = new Random(55); int[] ar = new int[19999];

for (int i = 0; i < ar.Length; i++) ar[i] = ran.Next();

Sorting run = new Sorting(ar);

View watch = new View(); // Создан объект run.onSort += new SortHandler(Display.barShow); run.onSort += new SortHandler(watch.nShow); run.sort();

Console.Write("\n");

}

}

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

100372610 ▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌▌◄

Событийный делегат SortHandler и переменная события onSort должны быть одинаково доступны в месте подписки на событие.

При генерации события целесообразно проверять значение переменной события. Эта переменная остается равной null, если на событие нет ни одной подписки.

Обратите внимание на тот факт, что генерация события в отличие от генерации исключения оформляется как обращение к методу. Тем самым после обработки события управление автоматически возвращается в точку, непосредственно следующую за оператором генерации события.

Контрольные вопросы

1.В чём основное назначение делегата?

2.Назовите этапы применения делегатов.

3.Члены каких видов могут присутствовать в делегате-типе?

4.Объясните назначение элементов объявления делегата-типа.

5.Как объявить ссылку с типом делегата?

6.Как создать экземпляр делегата?

7.Как аргументы можно использовать при обращении к конструктору делегата?

Делегаты и события

369

 

 

8.Где может размещаться объявление делегата-типа?

9.Каковы возможности свойств Method и Target?

10.Для чего применяются массивы делегатов?

11.Что такое многоадресный экземпляр делегата?

12.Какие средства поддерживают работу с многоадресными экземплярами делегатов?

13.Как получить массив делегатов из многоадресного делегата?

14.Что такое механизм обратного вызова?

15.Как используются делегаты для организации обратных вызовов?

16.Что такое анонимный метод?

17.Как специфицируется сигнатура анонимного метода?

18.Приведите пример размещения анонимного метода в обращении к методу, требующему обратных вызовов.

19.Что такое событие в языке C# ?

20.Объясните синтаксис оператора посылки сообщения.

21.Приведите формат объявления события.

22.Что такое переменная события?

23.Что определяет делегат, указанный в объявлении события?

24.Какие действия предусматривает подписка на события?

25.Назовите этапы работы с событиями.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]