- •А. А. Волосевич
- •Содержание
- •1. Общая характеристика платформы .Net
- •1.1. Инфраструктура платформы .Net
- •1.2. Версии платформы .Net
- •2. Общие концепции синтаксиса языка c#
- •3. Система типов clr и языка c#
- •4. Идентификаторы, ключевые слова и литералы
- •5. Выражения и операции
- •1. Первичные операции
- •2. Унарные операции
- •6. Операторы
- •6.1. Операторы объявления
- •6.2. Операторы выражений
- •6.3. Операторы перехода
- •6.4. Операторы выбора
- •6.5. Операторы циклов
- •6.6. Прочие операторы
- •7. Начальные сведения о массивах
- •8. Классы
- •8.1. Допустимые элементы класса
- •8.2. Модификаторы доступа для элементов и типов
- •8.3. Разделяемые классы
- •8.4. Использование класса
- •9. Методы
- •9.1. Описание метода
- •9.2. Вызов метода
- •9.3. Разделяемые методы
- •10. Свойства и индексаторы
- •11. Статические элементы и методы расширения
- •11.1. Статические элементы
- •11.2. Статические классы
- •11.3. Методы расширения
- •12. Конструкторы и инициализация объектов
- •13. Наследование классов
- •14. КлассSystem.Objectи иерархия типов
- •15. Структуры
- •16. Перечисления
- •17. Интерфейсы
- •18. Универсальные шаблоны
- •18.1. Универсальные классы и структуры
- •18.2. Ограничения на параметры универсальных типов
- •18.3. Универсальные методы
- •18.4. Ковариантность и контравариантность
- •19. Использование универсальных шаблонов
- •19.1. Кортежи
- •19.2. Типы, допускающие значение null
- •19.3. Прочие примеры универсальных шаблонов
- •20. Делегаты
- •21. Анонимные методы и лямбда-выражения
- •22. События
- •23. Перегрузка операций
- •24. Анонимные типы
- •25. Пространства имён
- •26. Генерация и обработка исключительных ситуаций
- •27. Директивы препроцессора
- •28. Документирование исходного кода
- •Литература
22. События
Событийно-ориентированное программирование(СОП) – парадигма программирования, в которой выполнение программы определяетсясобытиями– действиями пользователя (клавиатура, мышь), сообщениями других программ и потоков, событиями операционной системы. СОП, как правило, применяется при построении пользовательских интерфейсов и при программировании игр, в которых осуществляется управление множеством объектов.
Работу с событиями можно условно разделить на три этапа:
– объявлениесобытия (publishing);
– регистрацияполучателя события (subscribing);
– генерациясобытия (raising).
В языке C# событие можно объявить в пределах класса или структуры. Для этого используется ключевое словоeventи делегат, описывающий метод обработки события. Обычно этот делегат имеет тип возвращаемого значенияvoid1.
модификаторы event тип-делегата имя-события;
Встретив подобное объявление события, компилятор добавляет в класс или структуру private-поле с именемимя-событияи типомтип-делегата. Кроме этого, для обслуживания события компилятор создаёт два методаadd_Name()иremove_Name(), гдеName– имя события. Эти методы содержат код, добавляющий и удаляющий обработчик события в цепочку группового делегата, связанного с событием. Если программиста по каким-либо причинам не устраивает подход, используемый компилятором, он может реализовать собственную логику добавления и удаления обработчика события2. Для этого при объявлении события указывается блок, содержащий секцииaddиremove:
модификаторы event тип-делегата имя-события
{
add { операторы }
remove { операторы }
};
В блоке добавления и удаления обработчиков обычно размещается код, добавляющий или удаляющий метод в цепочку группового делегата. Поле-делегат в этом случае также должно быть явно объявлено в типе.
Для генерации события в требуемом месте кода помещается вызов в формате имя-события(фактические-аргументы). Предварительно обычно проверяют, назначен ли обработчик события. Генерация события может происходить только в том же типе, в котором событие объявлено3.
Приведём пример класса, содержащего объявление и генерацию события. Данный класс будет включать метод с целым параметром, устанавливающий значение поля класса. Если значение параметра отрицательно, генерируется событие, определённое в классе:
public delegate void Handler(int val);
publicclassExampleClass
{
privateint_field;
publicintField
{
get {return_field; }
set
{
_field = value;
if(value< 0)
{
// проверяем, есть ли обработчик
if (NegativeValueSet != null)
{
NegativeValueSet(value);
}
}
}
}
publiceventHandlerNegativeValueSet;
}
Рассмотрим этап регистрации получателя события. Чтобы отреагировать на событие, его надо ассоциировать с обработчиком события. Обработчиком может быть метод, приводимый к типу события (делегату). В качестве обработчика может выступать анонимный метод или лямбда-оператор. Назначение и удаление обработчиков события выполняется при помощи операторов+=и-=.
Используем класс ExampleClassи продемонстрируем назначение и удаление обработчиков событий:
publicclassMainClass
{
publicstaticvoidReaction(inti)
{
Console.WriteLine("Negative value = {0}", i);
}
publicstaticvoidMain()
{
varc =newExampleClass();
c.Field = -10; // нет обработчиков, нет реакции на событие
// назначаем обработчик
c.NegativeValueSet += Reaction;
c.Field = -20; // вывод: "Negative value = -20"
// назначаем ещё один обработчик в виде лямбда-выражения
c.NegativeValueSet += i => Console.WriteLine(i);
c.Field = -30; // вывод: "Negative value = -30" и "-30"
// удаляем первый обработчик
c.NegativeValueSet -= Reaction;
}
}
Платформа .NET предлагает средства стандартизации работы с событиями. В частности, для типов событий зарезервированы следующие делегаты:
public delegate void EventHandler(object sender, EventArgs e);
publicdelegatevoidEventHandler<T>(objectsender, T e)
where T : EventArgs;
Как видим, данные делегаты предполагают, что первым параметром будет выступать объект, в котором событие было сгенерировано. Второй параметр используется для передачи информации события. Это либо класс EventArgs, либо наследник этого класса с необходимыми полями.
Сама генерация события обычно выносится в отдельный виртуальный метод класса. В этом методе непосредственно перед генерацией события проверяется, установлен ли обработчик события. Перед проверкой можно создать копию события (это актуально в многопоточных приложениях).
Внесём изменения в код класса ExampleClass, чтобы работа с событиями соответствовала стандартам:
publicclassValueEventArgs:EventArgs
{
publicintNewValue {get;privateset; }
publicValueEventArgs(intnewValue)
{
NewValue = newValue;
}
}
publicclassExampleClass
{
privateint_field;
publicintField
{
get{return_field; }
set
{
_field = value;
if (value< 0)
{
OnNegativeValueSet(newValueEventArgs(value));
}
}
}
protectedvirtualvoidOnNegativeValueSet(ValueEventArgse)
{
EventHandler<ValueEventArgs> local = NegativeValueSet;
if(local !=null)
{
local(this, e);
}
}
publiceventEventHandler<ValueEventArgs> NegativeValueSet;
}
Создадим несколько полезных классов для упрощения работы с событиями. Часто для передачи информации события достаточно класса с единственным свойством. В этом случае можно использовать универсальный класс EventArgs<T>.
public class EventArgs<T> : EventArgs
{
publicT EventInfo {get;privateset; }
publicEventArgs(T eventInfo)
{
EventInfo = eventInfo;
}
}
Пример использования EventArgs<T>при описании события:
publiceventEventHandler<EventArgs<int>> NegativeValueSet;
Поместим в класс EventHelperдва метода расширения, упрощающих генерацию событий:
publicstaticclassEventHelper
{
publicstaticvoidRaise<T>(
thisEventHandler<EventArgs<T>> handler,
EventArgs<T> args,objectsender =null)
{
varlocal = handler;
if(local !=null)
{
local(sender, args);
}
}
publicstaticvoidRaise(thisEventHandlerhandler,
EventArgsargs,objectsender =null)
{
varlocal = handler;
if(local !=null)
{
local(sender, args);
}
}
}
Приведём пример использования метода расширения из класса EventHelper:
protectedvirtualvoidOnNegativeValueSet(EventArgs<int> e)
{
NegativeValueSet.Raise(e, this);
}