Учебное пособие (1077022), страница 14
Текст из файла (страница 14)
В консоль будет выведено:5 + 3 = 85 - 3 = 2Необходимо учитывать, что очередность вызовов метода в групповомделегате не гарантируется. То есть разработчик может быть уверен, чтооба метода a1 и a2 будут вызваны, но они могут быть вызваны впроизвольном порядке.Для добавления вызова метода к групповому делегату может бытьиспользован оператор «+=», а для удаления вызова метода из групповогоделегата оператор «-=».116Пример добавления и удаления вызова методов для группового делегата:Action<int, int> group2 = a1;Console.WriteLine("Добавление вызова метода к групповому делегату");group2 += a2;group2(10, 5);Console.WriteLine("Удаление вызова метода из группового делегата");group2 -= a1;group2(20, 10);Результат вывода в консоль:Добавление вызова метода к групповому делегату10 + 5 = 1510 - 5 = 5Удаление вызова метода из группового делегата20 - 10 = 10Групповые делегаты являются основой для реализации механизмасобытий.5.11 СобытияСобытия в языке C# реализуют механизм подписки/публикации.Любой класс может объявить события и вызывать их в необходимыемоменты времени.
Внешние классы могут прикрепить методы в качествеобработчиков событий, иначе говоря «подписаться на событие».Событияоснованынаобобщенныхделегатах,следовательно,обработчики событий должны соответствовать сигнатуре обобщенногоделегата для события.В языках С++ и Java существует большое количество библиотек,реализующих подход подписки/публикации, однако они реализованы ввиде внешних библиотек, а не в виде конструкции, встроенной в языкпрограммирования.Особенность событий языка C# заключается в том, что онисоответствуют механизму событий .NET-фреймворка. Например, событиенажатия на кнопку в технологии Windows Forms является событием языка117C#, создание обработчика события нажатия на кнопку происходит сиспользованием стандартного механизма событий в языке C#.Рассмотрим работу с событиями на основе фрагментов примера 9.Объявим делегат, на котором основано событие:public delegate void NewEventDelegate(string str);Делегат принимает один строковый параметр.
Делегат, на которомосновано событие, должен возвращать значение void.Событие объявлено в классе консольного приложения Program:public static event NewEventDelegate NewEvent;Событие объявляется как обычный элемент класса, NewEventфактически является специализированной переменной.Для события указана область видимости (public). Объявление staticозначает, что данное поле статическое, события также могут бытьобъявлены в виде нестатических полей. Далее указывается ключевое словоevent, затем идет наименование делегата, на котором основано событие, вданномпримереNewEventDelegate.Посленаименованияделегатауказывается имя события NewEvent, фактически, это имя переменной.События отображаются в IntelliSense в виде пиктограммы с«молнией» (рис.
18).Чтобыприкрепитьобработчикксобытиюиспользуетсяперегруженный оператор «+=», для открепления от события (удаленияобработчика события) – перегруженный оператор «-=»:Поскольку механизм событий основан на механизме групповыхделегатов, к событию может быть прикреплено несколько обработчиков.Гарантируется, что при вызове события будут вызваны все обработчики,но порядок их вызова не гарантируется. Таким образом, при написанииобработчиков событий, программист не должен рассчитывать, чтообработчикисобытийприкрепления к событию.обязательнобудутвызваныв порядкеих118Рис. 18.
Отображение события в IntelliSense.Вызов события производится как вызов обыкновенного метода, вскобках указываются параметры вызова. Однако при вызове событийсуществует одна особенность: если вызвать событие, к которому неприкрепленниодинNullReferenceException.обработчик,Длярешениятоэтойвозникнетпроблемыисключениеусобытийпереопределено сравнение с null. Если оператор сравнения события с nullвозвращает истину, значит к нему не прикреплен ни один из обработчиков.Поэтому перед вызовом события необходимо проводить проверку насравнение с null.Пример объявления обработчиков событий:public static void EventHandler1(string str){Console.WriteLine("Первый обработчик события: " + str);}public static void EventHandler2(string str){Console.WriteLine("Второй обработчик события: " + str);}Пример прикрепления обработчиков и вызова события://Этот вызов не сработает, так как не прикреплены обработчики события//NewEvent != null - проверка того, что к событию прикреплены обработчикиif (NewEvent != null) NewEvent("вызов события 0");//Прикрепление обработчика событияProgram.NewEvent += EventHandler1;if (Program.NewEvent != null) Program.NewEvent("вызов события 1");//Прикрепление второго обработчика события//Вызываются оба обработчика119Program.NewEvent += EventHandler2;if (Program.NewEvent != null) Program.NewEvent("вызов события 2");//Удаление второго обработчика событияProgram.NewEvent -= EventHandler2;if (Program.NewEvent != null) Program.NewEvent("вызов события 3");Результаты вывода в консоль:Первый обработчик события: вызов события 1Первый обработчик события: вызов события 2Второй обработчик события: вызов события 2Первый обработчик события: вызов события 3Рассмотренный пример, содержащий основы работы с событиями,необходим для понимания основ работы с событиями, однако, нужноотметить следующее: как правило, событие объявляется внутри класса и не являетсястатическим, а обработчики события прикрепляются снаружикласса; событие основывают на библиотечном делегате EventHandlerили других специализированных делегатах; обработчики событий часто задают в виде лямбда-выражений.Рассмотрим пример создания события с учетом данных особенностей.Он основан на стандартном делегате EventHandler, объявленном впространстве имен System:public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);Данный делегат – обобщенный.
Он возвращает тип void, что являетсянеобходимым условием для делегата события, и принимает два параметра.Первый параметр sender типа object содержит ссылку на объект,инициировавший событие (напомним, что object – ссылочный тип). Второйпараметр e обобщенного типа TEventArgs содержит произвольныепараметры события. В качестве обобщенного типа TEventArgs можетиспользоваться любой тип, включающий в себя описание параметровсобытия.120Пример класса, предназначенного для описания параметров события:usingusingusingusingSystem;System.Collections.Generic;System.Linq;System.Text;namespace Events{/// <summary>/// Параметры события/// </summary>public class NewGenericEventArgs : EventArgs{/// <summary>/// Конструктор/// </summary>/// <param name="param"></param>public NewGenericEventArgs(string param){this.NewGenericEventArgsParam = param;}/// <summary>/// Свойство, содержащее параметр/// </summary>public string NewGenericEventArgsParam { get; private set; }}}КлассNewGenericEventArgs–наследникклассаEventArgs.Библиотечный класс EventArgs базовый для классов, содержащих данные особытии, и объявлен в пространстве имен System.Runtime.InteropServices.Класс NewGenericEventArgs содержит автоопределяемое свойствоNewGenericEventArgsParam типа string, предназначенное для хранениястрокового параметра.
Также класс содержит конструктор, которыйинициализирует данное свойство.Пример класса издателя события NewGenericEventPublisher:usingusingusingusingSystem;System.Collections.Generic;System.Linq;System.Text;namespace Events{/// <summary>121/// Класс, содержащий событие/// </summary>public class NewGenericEventPublisher{/// <summary>/// Событие создается через обобщенный делегат/// </summary>public event EventHandler<NewGenericEventArgs> NewGenericEvent;/// <summary>/// Вызов события/// </summary>public void RaiseNewGenericEvent(string param){//Если у события есть подписчикиif (NewGenericEvent != null){//Вызов событияNewGenericEvent(this, new NewGenericEventArgs(param));}}}}ДляобъявлениясобытияприменяетсяобобщенныйделегатEventHandler. Класс NewGenericEventArgs используется в качестве классаобобщения для хранения параметров события.public event EventHandler<NewGenericEventArgs> NewGenericEvent;Класс NewGenericEventPublisher не содержит конструктор, поэтомуприменяется конструктор по умолчанию, не содержащий аргументов.Вызов события тестируется с помощью метода RaiseNewGenericEvent.Метод принимает строковый аргумент, который используется присоздании параметра события, экземпляра класса NewGenericEventArgs.
Вметодепроверяется,чтодлясобытиязаданыобработчики«NewGenericEvent != null», если обработчики заданы, то производитсявызов события.Данный класс является учебным примером, в нем вызов событиявыполняется с помощью тестового метода. В реальных проектах вызовысобытий могут производиться внутри методов класса при достиженииопределенных условий.122КлассомслушателясобытиябудетосновнойклассProgramконсольного приложения. Работа с событием происходит в основномметоде Main консольного приложения.Для вызова события необходимо сначала создать объект класса:NewGenericEventPublisher ne = new NewGenericEventPublisher();Далее к событию, являющемуся полем объекта класса, прикрепляетсяобработчик, который здесь задается в виде лямбда-выражения:ne.NewGenericEvent += new EventHandler<NewGenericEventArgs>((sender, e) =>{Console.WriteLine(e.NewGenericEventArgsParam);});Лямбда-выражение соответствует делегату EventHandler.
В лямбдавыражении в консоль выводятся строки параметров, передаваемых черезобъект класса NewGenericEventArgs.ДлявызовасобытиявэтомслучаеиспользуетсяметодRaiseNewGenericEvent:ne.RaiseNewGenericEvent("Событие NewGenericEventPublisher");В данном упрощенном примере вызов события происходит врезультате внешнего вызова метода RaiseNewGenericEvent. Однако, это несовсем естественный путь использования событий.
Предполагается, чтособытие будет вызываться внутри класса при срабатывании определенныхусловий, характеризующих, например, изменение состояния класса.В завершении рассмотрения событий нужно отметить, что несмотряна то, что данный механизм активно используется в языке C#, он можетвызывать проблему при сборке мусора. Проблема возникает за счет того,что издатель и слушатель события соединяются напрямую. Для решенияэтой проблемы применяется более сложный шаблон так называемых«слабых событий» (weak events), который использует более гибкиймеханизм соединения между издателем и слушателем события и не создает123проблем при сборке мусора.















