Один из наиболее современных подходов к построению приложений – подход, основанный на генерации и последующей обработке событий.
Событие — это переход объекта из одного состояния в другое. Например, нажатие клавиши, передвижение курсора мыши, изменение свойств какого-то объекта
Самое простое событие — это событие, сообщающее о начале или о завершении некоторой процедуры.
События генерируются в ответ на действия пользователя. Это может быть щелчок пользователем по кнопке, ввод данных в текстовое поле, и т.д.
Событие может быть сгенерировано и без взаимодействия с пользователем, например, при выполнении некоторого метода.
Реакция приложения на событие проявляется в выполнении некоторых действий. Для этого существуют обработчики событий – методы, которые непосредственно выполняет действия.
При возникновении одного и того же события, для объектов разных классов могут выполняться разные действия (вызываться различные обработчики).
Например, событие «Звонок». Реакция на это событие:
Как же программа узнает, что событие произошло, и какие обработчики этого события в разных классах нужно вызывать?
Поэтому есть нечто, что соединяет события и обработчики. Это подписка на события. При подписке на события указывается, в каком классе, какие обработчики должны выполняться в ответ на возникновение события.
Для нашего примера, есть две подписки на событие «Звонок»:
От события можно и отписаться. При этом событие происходит, но соответствующий обработчик не выполняется.
Делегат это класс. Объекты этого класса это прототипы функций, то есть функции с определенной сигнатурой.
Сигнатура функции — это сочетание названия типа, который функция возвращает, плюс название типов входящих параметров (в порядке следования).
При описании переменной типа делегат указывается ключевое слово delegate, а также функция, сигнатура которой совпадает с типом делегата. После этого делегат можно использовать для вызова указанной функции.
Данный делегат Del может представлять любую функцию типа void без возвращаемого значения и с одним параметром типа string:
public delegate void Del(string message); |
---|
Данный делегат MyDel может представлять любую функцию типа void без возвращаемого значения и без параметров:
public delegate void MyDel(); |
---|
Данный делегат Ddel может представлять любую функцию типа void без возвращаемого значения и с двумя параметрами: первый типа int, второй типа double.
public delegate void Ddel(int i, double j); |
---|
Фундаментальное отличие переменной-типа делегат от ранее рассмотренных состоит в том, что в качестве значения переменной-этого типа можно присвоить метод соответствующий определенной сигнатуре.
В любой метод можно предать в качестве параметра объект класса делегат, то есть можно в качестве параметра передать в метод функцию.
Для создания объекта класса делегатов используют конструктор:
делегат переменная =new делегат(объект.метод); |
---|
Метод, который передается как параметр делегата, должен иметь такую же сигнатуру, как и объявление делегата.
Пусть в классе MathClass описаны два метода с одинаковой сигнатурой:
В классе Program:
Объекты класса Del это методы. Объект d1 это фактически метод MultiplyNumbers, примененный к объекту m, а объект d2, это метод AddNumbers примененный к тому же объекту.
А теперь добавим два метода с другой сигнатурой. И объявим второй делегат.
Зачем все это нужно? Делегаты являются основой событий.
Событие представляет собой сообщение, посылаемое объектом, чтобы сигнализировать о свершении какого-либо действия. Это действие может быть вызвано в результате взаимодействия с пользователем, например при нажатии кнопки мыши, или может быть обусловлено логикой работы программы.
Объект, вызывающий событие, называется отправителем или издателем события. Объект, который следит за событием и реагирует на него, называется получателем или подписчиком события.
События имеют следующие свойства:
При обмене событиями классу отправителя событий не известен объект или метод, который будет получать (обрабатывать) сформированные отправителем события. Необходимо, чтобы между источником и получателем события имелся посредник. Именно для этого используются делегаты.
В основе механизма обработки событий лежат делегаты, поэтому прежде, чем объявить событие, необходимо объявить делегат. Объявление события похоже на объявление переменной типа делегата, но с добавлением в начале ключевого слова event. Для объявления события используется следующий синтаксис:
event делегат событие; |
---|
Чтобы сгенерировать событие, указывается имя события, а затем в круглых скобках через запятую перечисляются параметры события.
Прежде чем возбуждать событие, рекомендуется проверить, не равна ли null переменная, описывающая событие.
Для подписки на событие для какого-либо объекта используется оператор +=, после которого с помощью ключевого слова new вызывается конструктор делегата, в качестве параметра которого указывается имя метода, представляющего из себя обработчик события.
объект1.событие+=new делегат(объект2.метод); |
---|
Здесь:
объект1 – объект, инициирующий событие;
объект2 – объект, который подписывается на событие.
метод – обработчик события для объект2.
Для отмены подписки на события используется та же конструкция, только с оператором -=
Пусть определено три класса.
Первый класс будет считать до 10, и выводить значения счетчика на консоль, используя цикл.
Два других класса будут ждать, когда в первом классе счетчик досчитает, например, до 7.
После этого первый выведет на консоль фразу «Пора действовать, ведь уже 7!»,
Второй выведет фразу «Точно, уже 7!»
Проще говоря, при обнаружении значения 7, вызовутся по одному методу, соответственно для каждого класса.
Порядок написания кода программы:
Подготовим эти три простейших класса:
Класс ClassCounter и его метод Count() в котором будет выполняться счет.
Два других класса (с именами Handler_I и Handler_II), которые должны реагировать на возникновение события методами public void Message().
Абстрагируемся от программирования. Событие, которое мы хотим создать, будет представлять фразу "… счетчик считает. И как только он будет равен 7, должны выполниться действия". Значит, нам необходимо условие «как только он будет равен 7». Представим его при помощи условного оператора if.
В классе, в котором возникает событие, конструируем его.
Сначала определяем по методам, которые должны сработать при i=7 их сигнатуру (или прототип).
Сигнатура метода — это так называемая спецификация (или простыми словами «шаблон») какого-либо метода или методов. Представляет собой сочетание названия типа, который метод возвращает, плюс название типов входящих параметров в порядке следования.
События основаны на делегатах. А делегат, говоря очень простым языком — «переменная, хранящая ссылку на метод».
Наше событие будет ссылаться на два метода. Мы должны определить сигнатуры этих методов, и составить на основе этих сигнатур делегаты.
Если сигнатуры у обработчиков разные, то нужно два делегата, если одинаковые, то один делегат. У наших обработчиков сигнатуры одинаковые: нет выходного значения и нет параметров. Поэтому делегат будет один.
Определяем делегат (назовем его MethodContainer).
Далее, мы описываем событие при помощи ключевого слова event и связываем его с этим делегатом (MethodContainer), а, следовательно, c методами, имеющими такую же сигнатуру, как у делегата.
Событие должно быть public, т.к. его должны использовать разные классы, которым нужно как-то отреагировать (классы Handler_I и Handler_II).
Далее запустим наше событие onCount, в условии когда i=7
Событие создано. Методы, которые вызовет это событие, определены по сигнатурам и на основе их создан делегат. Событие, в свою очередь, создано на основе делегата. Пора показать событию onCount, какие же все-таки методы должны сработать (мы ведь указали только их сигнатуру).
Вернемся в точку входа программы main и создадим объекты трех наших классов.
Теперь укажем событию onCount, методы, которые должны запуститься.
Происходит это следующим образом:
КлассИлиОбъект.ИмяСобытия += КлассИлиОбъектЧейМетодДолженЗапуститься.МетодПодходящийПоСигнатуре |
Никаких скобочек после метода! Мы же не вызываем его, а просто указываем его название.
Класс или объект: указываем класс, если событие статическое, и объект, если объект был создан. В нашем примере все три объекта созданы, поэтому указываем объект.
Теперь осталось запустить метод Count класса ClassCounter и подождать, пока i станет равным 7.
Как только i=7, возникнет событие onCount по делегатуMethodContainer, который (в свою очередь) запустит методы Message(), которые были подписаны на событие.
Результат:
Внесем некоторые изменения.
Результат:
Еще раз порядок написания кода программы: