Справочник от Автор24
Поделись лекцией за скидку на Автор24

Делегаты, анонимные функции, события и лямбда-выражения

  • 👀 301 просмотр
  • 📌 251 загрузка
Выбери формат для чтения
Статья: Делегаты, анонимные функции, события и лямбда-выражения
Найди решение своей задачи среди 1 000 000 ответов
Загружаем конспект в формате doc
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Делегаты, анонимные функции, события и лямбда-выражения» doc
Лекция №5 Делегаты, анонимные функции, события и лямбда-выражения 1. Теоретические сведения Делегат объединяет в себе идеи - прототипа функции, указателя на функцию, передачи метода(функции) как аргумент в другой метод, возврата метода из метода, коллекция методов с возможностью добавлять и удалять методы с одинаковой сигнатурой и последующим вызовом их последовательно либо в порядке задаваемым разработчиком. Событие —создается на основе делегата, обычно является public - позволяет контролировать добавление или удаление методов в закрытый делегат, напоминает отношение между полями и свойствами, 1.1. Делегаты Делегат (delegate) — это объект, который может ссылаться на метод, т.е. создавая делегат, получаем объект, который содержит ссылку на метод. Кроме того, этот метод можно вызвать посредством соответствующей ссылки. Делегаты используются по двум основным причинам. Во-первых, делегаты обеспечивают поддержку функционирования событий. Во-вторых, во время выполнения программы один и тот же делегат можно использовать для вызова различных методов, которые точно не известны в период компиляции, просто заменив метод, на который ссылается этот делегат. Все делегаты представляют собой классы, которые неявным образом выводятся из класса System.Delegate. Делегат объявляется с помощью ключевого слова delegate. Общая форма объявления делегата имеет следующий вид: delegate <тип_возврата> <имя>{<список_параметров>); где элемент <тип_возврата> представляет собой тип значений, возвращаемых методами, которые этот делегат будет вызывать. Имя делегата указывается элементом <имя>. Параметры, принимаемые методами, которые вызываются посредством делегата, задаются с помощью элемента <список_параметров>. Делегат может вызывать только такие методы, у которых тип возвращаемого значения и список параметров (т.е. его сигнатура) совпадают с соответствующими элементами объявления делегата, исключением является ковариация и контрвариация. Делегат позволяет собрать композицию из статических методов, так и динамических (экземпляров классов). 1.1.1. Вызов статических методов с помощью делегата Пример 1. Рассмотрим простой пример, демонстрирующий использование делегата для вызова статических методов. Результат выполнения примера представлен на рис. 1. /* объявляем делегата с именем strMod, который принимает один параметр типа string и возвращает string-значение */ delegate string strMod(string stx); /* В классе DelegateTest объявляются три статических метода, сигнатура которых совпадает с сигнатурой, заданной делегатом */ class DelegateTest { // Метод заменяет пробелы дефисами public static string replaceSpaces(string a) { Console.WriteLine(" Замена пробелов дефисами."); return a.Replace(' ', '-'); } // Метод удаляет пробелы public static string removeSpaces(string a) { string temp = ""; Console.WriteLine(" У даление пробелов."); for (int i = 0; i < a.Length; i++) if (a[i] != ' ') temp += a[i]; return temp; } // Метод реверсирует строку public static string reverse(string a) { string temp = ""; Console.WriteLine(" Реверсирование строки."); for (int j = 0, i = a.Length - 1; I >= 0; i--, j++) temp += a[i]; return temp; } } class Program { static void Main(string[] args) { string str; /* Создание делегата и вызов метода посредством делегата в качестве параметра делегату передается имя метода replaceSpaces() */ strMod strOp = new strMod(DelegateTest.replaceSpaces); /*метод replaceSpaces() вызывается посредством экземпляра делегата с именем strOp, т.к. экземпляр strOp ссылается на метод replaceSpaces(), то вызывается метод replaceSpaces()*/ str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str + “\n”); //Создание делегата и вызов метода посредством делегата strOp = new strMod(DelegateTest.removeSpaces); str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str); Console.WriteLine(); Создание делегата и вызов метода посредством делегата strOp = new strMod(DelegateTest.reverse); str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } 1.1.2. Вызов методов экземпляра класса с помощью делегата Несмотря на то, что в предыдущем примере используются статические методы, делегат может также ссылаться на методы экземпляров класса, при этом он должен использовать объектную ссылку. Пример 2. Вот как выглядит предыдущая программа, переписанная с целью инкапсуляции операций над строками внутри класса StringOps. delegate string strMod(string stx); // Объявляем делегата class StringOps { • Метод заменяет пробелы дефисами public string replaceSpaces(string a) { Console.WriteLine(" Замена пробелов дефисами."); return a.Replace(' ', '-'); } • Метод удаляет пробелы public string removeSpaces(string a) { string temp = ""; Console.WriteLine(" У даление пробелов."); for (int i = 0; i < a.Length; i++) if (a[i] != ' ') temp += a[i]; return temp; } // Метод реверсирует строку public string reverse(string a) { string temp = ""; Console.WriteLine(" Реверсирование строки."); for (int j = 0, i = a.Length - 1; i >= 0; i--, j++) temp += a[i]; return temp; } class Program { static void Main(string[] args) { Console.Title = " Пример №2"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; string str; StringOps so = new StringOps(); // Создаем экземпляр класса StringOps • Создание делегата и вызов метода посредством делегата strMod strOp = new strMod(so.replaceSpaces); str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str); Console.WriteLine(); • Создание делегата и вызов метода посредством делегата strOp = new strMod(so.removeSpaces); str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str); Console.WriteLine(); • Создание делегата и вызов метода посредством делегата strOp = new strMod(so.reverse); str = strOp(" Это простой тест."); Console.WriteLine(" Результирующая строка: " + str); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Результаты выполнения этой программы совпадают с результатами предыдущей версии, но в этом случае делегат ссылается на методы экземпляра класса StringOps. 1.1.3. Многоадресная передача Одна из самых интересных возможностей делегата — поддержка многоадресной передачи (multicasting). Многоадресная передача — это способность создавать список вызовов (или цепочку вызовов) методов, которые должны автоматически вызываться при вызове делегата. Для создания цепочки вызовов достаточно создать экземпляр делегата, а затем для добавления методов в эту цепочку использовать оператор "+=". Для удаления метода из цепочки используется оператор " -= ". (можно для добавления и удаления методов использовать в отдельности операторы "+", " - " и "=", но чаще применяются составные операторы "+=" и "-=".) Делегат с многоадресной передачей имеет одно ограничение: он должен возвращать тип void. Пример 3. Рассмотрим пример многоадресной передачи. Это — переработанный вариант предыдущих примеров, в котором тип string для значений, возвращаемых методами обработки строк, заменен типом void, а для возврата модифицированных строк используется ref-параметр. Результат выполнения примера представлен на рис. 2. delegate void strMod(ref string str); // Объявляем делегата class StringOps { { • Метод заменяет пробелы дефисами public • • • static void replaceSpaces(ref string a) { Console.WriteLine(" Замена пробелов дефисами."); a = a.Replace(' ', '-'); } • Метод удаляет пробелы public static void removeSpaces(ref string a) { string temp = ""; Console.WriteLine(" У даление пробелов."); for (int i = 0; i < a.Length; i++) if (a[i] != ' ') temp += a[i]; a = temp; } // Метод реверсирует строку public static void reverse(ref string a) { string temp = ""; Console.WriteLine(" Реверсирование строки."); for (int j = 0, i = a.Length - 1; i >= 0; i--, j++) temp += a[i]; a = temp; } } class Program { static void Main(string[] args) { Console.Title = " Пример №3"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; • Создаем экземпляры делегатов strMod strOp; strMod replaceSp = new strMod(StringOps.replaceSpaces); strMod removeSp = new strMod(StringOps.removeSpaces); strMod reverseStr = new strMod(StringOps.reverse); string str = "Это простой тест."; • Организация многоадресной передачи strOp = replaceSp; strOp += reverseStr; • Вызов делегата с многоадресной передачей strOp(ref str); Console.WriteLine(" Результирующая строка: " + str); Console.WriteLine(); • У даляем метод замены пробелов и добавляем метод их удаления strOp -= replaceSp; strOp += removeSp; str = "Это простой тест."; // Восстановление исходной строки • Вызов делегата с многоадресной передачей strOp(ref str); Console.WriteLine(" Результирующая строка: " + str); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Рис. 2. Результат выполнения примера №3. В методе Main() создаются четыре экземпляра делегата. Первый, strOp, имеет null-значение. Три других ссылаются на методы модификации строк. Затем организуется делегат для многоадресной передачи, который вызывает методы removeSpaces() и reverse(), с помощью операторов программы: strOp = repiaceSp; strOp += reverseStr; // в цепочку вызовов добавляется ссылка reverseStr Цепочки вызовов, организованные с помощью делегата, — мощный механизм, который позволяет определять набор методов, выполняемых "единым блоком". Цепочки делегатов имеют особое значение для событий. 1.1.4. Анонимные методы и лямбда-выражения Метод, на который ссылается делегат, часто имеет отношение только к конкретной специфической ситуации (например, необходимо указать поле сортировки в структуре или классе). В этом случае для того чтобы не создавать избыточный код, можно воспользоваться анонимной функцией. Анонимная функция, по существу, представляет собой безымянный кодовый блок, передаваемый конструктору делегата. Преимущество анонимной функции состоит, в ее простоте. Начиная с версии 3.0, в C# предусмотрены две разновидности анонимных функций - анонимные методы и лямбда-выражения: using System; using System.Collections.Generic;   namespace ConsoleApplication1 { delegate int Sum(int number);   class Program { static Sum SomeVar() { int result = 0;   // Вызов анонимного метода Sum del = delegate (int number) { for (int i = 0; i <= number; i++) result += i; return result; }; return del; }   static void Main() { Sum del1 = SomeVar();   for (int i = 1; i <= 5; i++) { Console.WriteLine("Cумма {0} равна: {1}",i,del1(i)); }   Console.ReadLine(); } } } Обратите внимание на получившийся результат. Локальная переменная, в область действия которой входит анонимный метод, называется внешней переменной. Такие переменные доступны для использования в анонимном методе. И в этом случае внешняя переменная считается захваченной. Захваченная переменная существует до тех пор, пока захвативший ее делегат не будет собран в "мусор". Поэтому если локальная переменная, которая обычно прекращает свое существование после выхода из кодового блока, используется в анонимном методе, то она продолжает существовать до тех пор, пока не будет уничтожен делегат, ссылающийся на этот метод. Захват локальной переменной может привести к неожиданным результатам, как в приведенном выше примере, где локальная переменная result не обнуляется после каждого вызова анонимного метода. В результате получается необычный результат суммы чисел. Начиная с C# 3.0, доступен новый синтаксис для назначения реализации кода делегатам, называемый лямбда-выражениями (lambda expression). Лямбда-выражения могут использоваться везде, где есть параметр типа делегата. Во всех лямбда-выражениях применяется новый лямбда-оператор =>, который разделяет лямбда-выражение на две части. В левой его части указывается входной параметр (или несколько параметров), а в правой части — тело лямбда-выражения. Оператор => иногда описывается такими словами, как "следует", "переходит" или "становится". В одиночном лямбда-выражении часть, находящаяся справа от оператора =>, воздействует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения лямбда-оператора. Ниже приведена общая форма одиночного лямбда-выражения: (параметр1…параметрN) => выражение delegate bool bStringCompare(string s1, string s2); static void Main(string[] args) {   bStringCompare sc = (s1, s2) => s1 == s2; if (sc("text", "text")) Console.WriteLine("Строки равны"); } struct People { public People(string FName, string LName, int bdYear) { this.FName = FName; this.LName = LName; this.bdYear = bdYear; }   public string FName, LName; public int bdYear;   public override string ToString() { return "Person: " + FName + " " + LName + " " + bdYear; } } class Program { delegate bool bStringCompare(string s1, string s2); static void Main(string[] args) { List mylist = new List { new People("Petya", "Petroff", 2001), new People("Ivan", "Ivanov", 2000) };   mylist.Sort((a, b) => a.FName.CompareTo(b.FName));   foreach (People p in mylist) Console.WriteLine(p);   bStringCompare sc = (s1, s2) => s1 == s2; if (sc("text", "text")) Console.WriteLine("Строки равны"); } } Второй разновидностью лямбда-выражений является блочное лямбда-выражение. Для такого лямбда-выражения характерны расширенные возможности выполнения различных операций, поскольку в его теле допускается указывать несколько операторов. bStringCompare sc = (s1, s2) => { Console.WriteLine(s1 + " " + s2); return s1 == s2; };   if (sc("text", "text")) Console.WriteLine("Строки равны"); Помимо возможности использовать несколько операторов, блочное лямбда-выражение, практически ничем не отличается от одиночного лямбда-выражения. 1.2. События На основе делегатов построено еще одно важное средство С#: событие (event) — это по сути автоматическое уведомление о выполнении некоторого действия. События работают следующим образом. Объект, которому необходима информация о некотором событии, регистрирует обработчик для этого события. Когда ожидаемое событие происходит, вызываются все зарегистрированные обработчики. Обработчики событий представляются делегатами. События — это члены класса, которые объявляются с использованием ключевого слова event. Наиболее распространенная форма объявления события имеет следующий вид: event <событийный_делегат> <объект>; где элемент <событийный_делегат> означает имя делегата, используемого для поддержки объявляемого события, а элемент <объект> — это имя создаваемого событийного объекта. 1.2.1. Создание событий Пример 4. Рассмотрим простой пример, демонстрирующий использование простейшего события. delegate void MyEventHandler(); // Объявляем делегата для события // Объявляем класс события class MyEvent { public event MyEventHandler SomeEvent; // Этот метод вызывается для генерирования события public void OnSomeEvent() { if (SomeEvent != null) SomeEvent(); } } class EventDemo { // Обработчик события public static void handler() { Console.WriteLine(" Произошло событие."); } } class Program { static void Main(string[] args) { Console.Title = " Пример №4"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); // Добавляем метод handler() в список события evt.SomeEvent += new MyEventHandler(EventDemo.handler); // Генерируем событие evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Программа начинается с объявления делегата для обработчика события: delegate void MyEventHandler(); Так как все события активизируются посредством делегата, то событийный делегат определяет сигнатуру для события. В данном случае параметры отсутствуют, однако событийные параметры разрешены. Поскольку события обычно предназначены для многоадресной передачи, они должны возвращать значение типа void. Затем создается класс события MyEvent, в котором в первую очередь объявляется событийный объект SomeEvent: public event MyEventHandler SomeEvent; Кроме того, внутри класса MyEvent объявляется метод OnSomeEvent(), который в этой программе вызывается, чтобы сигнализировать о событии. Телом метода является оператор, который вызывает обработчик события посредством делегата SomeEvent: if(SomeEvent != null) SomeEvent(); Обработчик события вызывается только в том случае, если делегат SomeEvent не равен значению null. Поскольку другие части программы, чтобы получить уведомлении о событии, должны зарегистрироваться, можно сделать так, чтобы метод OnSomeEvent() был вызван до регистрации любого обработчика события. Чтобы предотвратить вызов null-объекта, событийный делегат необходимо протестировать и убедиться в том, что он не равен null-значению. • классе EventDemo создается обработчик события handler(), который просто отображает сообщение. • методе Main() создается объект класса MyEvent, MyEvent evt = new MyEvent(); а метод handler() регистрируется в качестве обработчика этого события: evt.SomeEvent += new MyEventHandler(handler); Обработчик добавляется в список с использованием составного оператора "+=". Следует отметить, что события поддерживают только операторы "+=" и " -= ". В нашем примере метод handler() является статическим, но в общем случае обработчики событий могут быть методами экземпляров классов. При выполнении оператора evt.OnSomeEvent(); "происходит" событие. При вызове метода OnSomeEvent() вызываются все зарегистрированные обработчики событий. В данном примере зарегистрирован только один обработчик. Результат выполнения программы представлен на рис. 3. Рис. 3. Результат выполнения примера №4. Подобно делегатам события могут предназначаться для многоадресной передачи. В этом случае на одно уведомление о событии может отвечать несколько объектов. Пример 5. Рассмотрим простой пример, демонстрирующий использование простейшего события, предназначенного для многоадресной передачи. В этом примере создаются два дополнительных класса X и Y, в которых также определяются обработчики событий, совместимые с сигнатурой делегата MyEventHandler. Следовательно, эти обработчики могут стать частью цепочки событийных вызовов. Так как обработчики в классах X и Y не являются статическими, то сначала должны быть созданы объекты каждого класса, после чего в цепочку событийных вызовов должен быть добавлен обработчик , связанный с каждым экземпляром класса. Результат выполнения программы представлен на рис. 4. delegate void MyEventHandler(); // Объявляем делегата для события // Объявляем класс события class MyEvent { public event MyEventHandler SomeEvent; // Этот метод вызывается для генерирования события public void OnSomeEvent() { if (SomeEvent != null) SomeEvent(); } } class X { public void Xhandler() { Console.WriteLine(" Событие, полученное объектом X."); } } class Y { public void Yhandler() { Console.WriteLine(" Событие, полученное объектом Y."); } } class EventDemo { public static void handler() { Console.WriteLine(" Событие, полученное классом EventDemo."); } } class Program { static void Main(string[] args) { Console.Title = " Пример №5"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); X xOb = new X(); Y yOb = new Y(); // Добавляем обработчики в список события evt.SomeEvent += new MyEventHandler(EventDemo.handler); evt.SomeEvent += new MyEventHandler(xOb.Xhandler); evt.SomeEvent += new MyEventHandler(yOb.Yhandler); • Генерируем событие evt.OnSomeEvent(); Console.WriteLine(); • У даляем один обработчик evt.SomeEvent -= new MyEventHandler(xOb.Xhandler); evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Рис. 4. Результат выполнения примера №5. 1.2.2. Методы, используемые в качестве обработчиков событий Несмотря на то, что и методы экземпляров классов, и статические методы могут служить обработчиками событий, в их использовании в этом качестве есть существенные различия. Если в качестве обработчика используется статический метод, уведомление о событии применяется к классу (и неявно ко всем объектам этого класса). Если же в качестве обработчика событий используется метод экземпляра класса, события посылаются к конкретным экземплярам этого класса. Следовательно, каждый объект класса, который должен получать уведомление о событии, необходимо регистрировать в отдельности. На практике в большинстве случаев роль обработчиков событий играют методы экземпляров классов. 1.2.2.1. Метод экземпляра класса в роли обработчика события Пример 6. В нижеприведенной программе создается класс X, в котором в качестве обработчика событий определен метод экземпляра. Это значит, что для получения информации о событиях каждый объект класса X необходимо регистрировать отдельно. Для демонстрации этого факта программа готовит уведомление о событии для многоадресной передачи трем объектам типа X. Результат выполнения программы представлен на рис. 5. delegate void MyEventHandler(); // Объявляем делегата для события // Объявляем класс события class MyEvent { public event MyEventHandler SomeEvent; // Этот метод вызывается для генерирования события public void OnSomeEvent() { if(SomeEvent != null) SomeEvent(); } } class X { int id; public X(int x) { id = x; } // Метод экземпляра, используемый в качестве обработчика событий public void Xhandler() { Console.WriteLine(" Событие принято объектом " + id); } } class Program { static void Main(string[] args) { Console.Title = " Пример №6"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); X ol = new X(1); X o2 = new X(2); X o3 = new X(3); evt.SomeEvent += new MyEventHandler(ol.Xhandler); evt.SomeEvent += new MyEventHandler(o2.Xhandler); evt.SomeEvent += new MyEventHandler(o3.Xhandler); // Генерируем событие evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Рис. 5. Результат выполнения примера №6. Как подтверждают эти результаты, каждый объект заявляет о своей заинтересованности в событии и получает о нем отдельное уведомление. 1.2.2.2. Статический метод класса в роли обработчика события Если же в качестве обработчика событий используется статический метод, то, как показано в следующей программе, события обрабатываются независимо от объекта. Пример 7. При использовании в качестве обработчиков событий статического метода уведомление о событиях получает класс. Результат выполнения программы представлен на рис. 6. delegate void MyEventHandler(); // Объявляем делегата для события // Объявляем класс события class MyEvent { public event MyEventHandler SomeEvent; // Этот метод вызывается для генерирования события public void OnSomeEvent() { if(SomeEvent != null) SomeEvent(); } } class X { // Это статический метод, используемый в качестве обработчика события public static void Xhandler() { Console.WriteLine(" Событие получено классом ."); } } class Program { static void Main(string[] args) { Console.Title = " Пример №7"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); evt.SomeEvent += new MyEventHandler(X.Xhandler); // Генерируем событие evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } Рис. 6. Результат выполнения примера №7. В программе не создается ни одного объекта типа X. Но поскольку handler() — статический метод класса X, его можно связать с событием SomeEvent и обеспечить его выполнение при вызове метода OnSomeEvent(). 1.2.3. Использование событийных средств доступа Предусмотрены две формы записи инструкций, связанных с событиями. Форма, используемая в предыдущих примерах, обеспечивала создание событий, которые автоматически управляют списком вызова обработчиков, включая такие операции, как добавление обработчиков в список и удаление их из списка. Таким образом, можно было не беспокоиться о реализации операций по управлению этим списком. Поэтому такие типы событий, безусловно, являются наиболее применимыми. Однако можно и самим организовать ведение списка обработчиков событий, чтобы, например, реализовать специализированный механизм хранения событий. Чтобы управлять списком обработчиков событий, используйте вторую форму event-инструкции, которая позволяет использовать средства доступа к событиям. Эти средства доступа дают возможность управлять реализацией списка обработчиков событий. Форма имеет следующий вид: event <событийный_делегат> <имя_события> { add { // Код добавления события в цепочку событий } remove { // Код удаления события из цепочки событий } } Эта форма включает два средства доступа к событиям: add и remove. Средство доступа add вызывается в случае, когда с помощью оператора "+=" в цепочку событий добавляется новый обработчик, а средство доступа remove вызывается, когда с помощью оператора " - = " из цепочки событий удаляется новый обработчик. Средство доступа add или remove при вызове получает обработчик, который необходимо добавить или удалить, в качестве параметра. Этот параметр, как и в случае использования других средств доступа, называется value. При реализации средств доступа add и remove можно задать собственную схему хранения обработчиков событий. Например, для этого можно использовать массив, стек или очередь. Пример 8. Рассмотрим пример использования событийных средств доступа. Здесь для хранения обработчиков событий взят массив. Поскольку этот массив содержит три элемента, в любой момент времени в событийной цепочке может храниться только три обработчика событий. Результат выполнения программы представлен на рис. 7. delegate void MyEventHandler(); // Объявляем делегат для события // Объявляем класс события для хранения трех обработчиков событий class MyEvent { MyEventHandler[] evnt = new MyEventHandler[3]; public event MyEventHandler SomeEvent { • Добавляем обработчик события в список add { int i; for (i = 0; i < 3; i++) if (evnt[i] == null) { evnt[i] = value; break; } if (i == 3) Console.WriteLine(" Список обработчиков событий полон."); } • У даляем обработчик события из списка remove { int i; for (i = 0; i < 3; i++) if (evnt[i] == value) { evnt[i] = null; break; } if (i == 3) Console.WriteLine(" Обработчик события не найден."); } } • Этот метод вызывается для генерирования событий public void OnSomeEvent() { for (int i = 0; i < 3; i++) if (evnt[i] != null) evnt[i](); } • Создаем классы, которые используют делегата MyEventHandler class W { public void Whandler() { Console.WriteLine(" Событие получено объектом W."); } } class X { public void Xhandler() { Console.WriteLine(" Событие получено объектом X."); } } class Y { public void Yhandler() { Console.WriteLine(" Событие получено объектом Y."); } } class Z { public void Zhandler() { Console.WriteLine(" Событие получено объектом Z."); } } class Program { static void Main(string[] args) { Console.Title = " Пример №5"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); W wOb = new W(); X xOb = new X(); Y yOb = new Y(); Z zOb = new Z(); // Добавляем обработчики в список Console.WriteLine(" Добавление обработчиков событий."); evt.SomeEvent += new MyEventHandler(wOb.Whandler); evt.SomeEvent += new MyEventHandler(xOb.Xhandler); evt.SomeEvent += new MyEventHandler(yOb.Yhandler); • Этот обработчик сохранить нельзя — список полон evt.SomeEvent += new MyEventHandler(zOb.Zhandler); Console.WriteLine(); • Генерируем события evt.OnSomeEvent(); Console.WriteLine(); // У даляем обработчик из списка Console.WriteLine(" У даляем обработчик xOb.Xhandler."); evt.SomeEvent -= new MyEventHandler(xOb.Xhandler); evt.OnSomeEvent(); Console.WriteLine(); // Пытаемся удалить его еще раз Console.WriteLine(" Попытка повторно удалить обработчик xOb.Xhandler."); evt.SomeEvent -= new MyEventHandler(xOb.Xhandler); evt.OnSomeEvent(); Console.WriteLine(); // Теперь добавляем обработчик Zhandler Console.WriteLine(" Добавляем обработчик zOb.Zhandler."); evt.SomeEvent += new MyEventHandler(zOb.Zhandler); evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } } Рис. 7. Результат выполнения примера №8. В начале программы определяется делегат обработчика события MyEventHandler. В классе MyEvent объявляется трехэлементный массив обработчиков событий evnt: MyEventHandler[] evnt = new MyEventHandler[3]; Этот массив предназначен для хранения обработчиков событий, которые добавлены в цепочку событий. Элементы массива evnt инициализируются null-значениями по умолчанию. При добавлении в список обработчика событий вызывается add-средство, и ссылка на этот обработчик (содержащаяся в параметре value) помещается в первый встретившийся неиспользуемый элемент массива evnt. Если свободных элементов нет, выдается сообщение об ошибке . Поскольку массив evnt рассчитан на хранение лишь трех элементов, он может принять только три обработчика событий. При удалении заданного обработчика событий вызывается remove-средство, и в массиве evnt выполняется поиск ссылки на обработчик , переданной в параметре value. Если ссылка найдена, в соответствующий элемент массива помещается значение null, что равнозначно удалению обработчика из списка. При генерировании события вызывается метод OnSomeEvent(). Он в цикле просматривает массив evnt, вызывая по очереди каждый обработчик событий. 1.2.4. Рекомендации по обработке событий в среде .NET Framework С# позволяет программисту создавать события любого типа. Однако в целях компонентной совместимости со средой .NET Framework необходимо следовать рекомендациям Microsoft. Центральное место занимает требование того, чтобы обработчики событий имели два параметра. Первый должен быть ссылкой на объект, который будет генерировать событие. Второй должен иметь тип EventArgs и содержать остальную информацию, необходимую обработчику, то есть .NET-совместимые обработчики событий должны иметь следующую общую форму записи: void handler(object source, EventArgs arg) { // . . . } где параметр source передается вызывающим кодом, параметр типа EventArgs содержит дополнительную информацию, которую в случае ненадобности можно проигнорировать. Класс EventArgs не содержит полей, которые используются при передаче дополнительных данных обработчику; он используется в качестве базового класса, из которого можно выводить класс, содержащий необходимые поля. Так как многие обработчики обходятся без дополнительных данных, в класс EventArgs включено статическое поле Empty, которое задает объект, не содержащий никаких данных. Пример 9. Нижеприведенный пример демонстрирует использование .NET-совместимого события. В производном классе MyEventArgs (базовый класс EventArgs) добавлено только одно "собственное" поле — eventnum. В соответствии с требованиями .NET Framework делегат для обработчика событий MyEventHandler принимает два параметра: первый из них представляет собой объектную ссылку на генератор событий, а второй — ссылку на класс EventArgs или производный от класса EventArgs (используется ссылка на объект типа MyEventArgs). Результат выполнения программы представлен на рис. 8. class MyEventArgs : EventArgs { public int eventnum; } // Объявляем делегат для события delegate void MyEventHandler(object source, MyEventArgs arg); // Объявляем класс события class MyEvent { static int count = 0; public event MyEventHandler SomeEvent; // Этот метод генерирует SomeEvent-событие public void OnSomeEvent() { MyEventArgs arg = new MyEventArgs(); if (SomeEvent != null) { arg.eventnum = count++; SomeEvent(this, arg); } } } class X { public void handler(object source, MyEventArgs arg) { Console.WriteLine(" Событие " + arg.eventnum + " получено объектом X."); Console.WriteLine(" Источником является класс " + source + "."); Console.WriteLine(); } } class Y { public void handler(object source, MyEventArgs arg) { Console.WriteLine(" Событие " + arg.eventnum + " получено объектом Y."); Console.WriteLine(" Источником является класс " + source + "."); Console.WriteLine(); } } namespace Primer9 { class Program { static void Main() { Console.Title = " Пример №9"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; X obi = new X(); Y ob2 = new Y(); MyEvent evt = new MyEvent(); • Добавляем обработчик handler() в список событий evt.SomeEvent += new MyEventHandler (obi .handler) ; evt.SomeEvent += new MyEventHandler(ob2.handler); • Генерируем событие evt.OnSomeEvent(); evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } } Рис. 8. Результат выполнения примера №9. 1.2.5. Использование встроенного делегата EventHandler Для многих событий параметр типа EventArgs не используется. Для упрощения процесса создания кода в таких ситуациях среда .NET Framework включает встроенный тип делегата, именуемый EventHandler. Его можно использовать для объявления обработчиков событий, которым не требуется дополнительная информация. Пример 10. Нижеприведенный пример демонстрирует использование встроенного делегата EventHandler. Результат выполнения программы представлен на рис. 9. class MyEvent { public event EventHandler SomeEvent; // Этот метод вызывается для генерирования SomeEvent-событие public void OnSomeEvent() { if (SomeEvent != null) SomeEvent(this, EventArgs.Empty); } } namespace Primer10 { class Program { public static void handler(object source, EventArgs arg) { Console.WriteLine(" Событие произошло."); Console.WriteLine(" Источником является класс " + source + "."); } static void Main() { Console.Title = " Пример №10"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; MyEvent evt = new MyEvent(); • Добавляем обработчик handler() в список событий evt.SomeEvent +=new EventHandler(handler); • Генерируем событие evt.OnSomeEvent(); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } } Рис. 9. Результат выполнения примера №10. 1.2.6. Пример использования событий События часто используются в таких средах с ориентацией на передачу сообщений, как OC Windows, в которой программа просто ожидает до тех пор, пока не получит сообщение, а затем выполняет соответствующие действия. Такая архитектура прекрасно подходит для обработки событий в стиле языка С#, позволяя создавать обработчики событий для различных сообщений и просто вызывать обработчик при получении определенного сообщения. Например, с некоторым событием можно было бы связать сообщение, получаемое в результате щелчка левой кнопкой мыши. Тогда после щелчка левой кнопкой мыши все зарегистрированные обработчики будут уведомлены о приходе этого сообщения. Пример 11. Рассмотрим пример обработки события , связанного с нажатием клавиши на клавиатуре. Событие называется KeyPress, и при каждом нажатии клавиши оно генерируется посредством вызова метода OnKeyPress(). Программа начинается с объявления класса KeyEventArgs, который используется для передачи сообщения о нажатии клавиши обработчику событий . Затем делегат KeyHandler определяет обработчик для событий , связанных с нажатием клавиши на клавиатуре. Эти события инкапсулируются в классе KeyEvent. Программа для обработки нажатий клавиш создает два класса: ProcessKey и CountKeys. Класс ProcessKey включает обработчик с именем keyhandler(), который отображает сообщение о нажатии клавиши. Класс CountKeys предназначен для хранения текущего количества нажатых клавиш. В методе Main() создается объект класса KeyEvent. Затем создаются объекты классов ProcessKey и CountKeys, а ссылки на их методы keyhandler() добавляются в список вызовов , реализуемый с помощью событийного объекта kevt.KeyPress. Затем начинает работать цикл, в котором при каждом нажатии клавиши вызывается метод kevt.OnKeyPress(), в результате чего зарегистрированные обработчики уведомляются о событии. Результат выполнения программы представлен на рис. 10. • Объявляем собственный класс EventArgs, который будет хранить код клавиши class KeyEventArgs : EventArgs { public char ch; } • Объявляем делегат для события delegate void KeyHandler(object source, KeyEventArgs arg); // Объявляем класс события, связанного с нажатием клавиши на клавиатуре class KeyEvent { public event KeyHandler KeyPress; // Этот метод вызывается при нажатии какой-нибудь клавиши public void OnKeyPress(char key) { KeyEventArgs k = new KeyEventArgs(); if(KeyPress != null) { k.ch = key; KeyPress(this, k ); } } } // Класс, который принимает уведомления о нажатии клавиши class ProcessKey { public void keyhandler(object source, KeyEventArgs arg) { Console.WriteLine("Получено сообщение о нажатии клавиши : " + arg.ch); } } // Еще один класс, который принимает уведомления о нажатии клавиши class CountKeys { public int count = 0; public void keyhandler(object source, KeyEventArgs arg) { count++; } } namespace Primer11 { class Program { static void Main() { Console.Title = " Демонстрация события о нажатии клавиши"; Console.BackgroundColor = ConsoleColor.White; Console.Clear(); Console.ForegroundColor = ConsoleColor.Black; KeyEvent kevt = new KeyEvent(); ProcessKey pk = new ProcessKey(); CountKeys ck = new CountKeys(); char ch; kevt.KeyPress += new KeyHandler(pk.keyhandler); kevt.KeyPress += new KeyHandler(ck.keyhandler); Console.WriteLine("Введите несколько символов. " + "Для останова введите точку."); do { ch = (char) Console.Read(); kevt.OnKeyPress(ch); } while(ch != '.' ); Console.WriteLine(" Было нажато " + ck.count + " клавиш."); Console.Write("Для завершения работы приложения нажмите клавишу "); Console.Read(); } } } Рис. 10. Результат выполнения примера №11. Пример асинхронного вызова событий using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Runtime.Remoting.Messaging;   namespace ConsoleApplication10 { class TestHarness {   static void Main(string[] args) { var raiser = new SomeClass();   // Emulate some event listeners raiser.SomeEvent += (sender, e) => { Console.WriteLine("#1 Received First asnyc event"); }; raiser.SomeEvent += (sender, e) => { Console.WriteLine("#2 Received Second async event"); Console.WriteLine("#2 Blocking for 5 second async event..."); for (int i = 0; i < 10;i++) { Thread.Sleep(250); Console.Write("."); } Console.WriteLine("\n#2 Finished blocking event"); };   // Raise the event, see the effects raiser.OnSomeEvent(); Console.WriteLine("We already here!\n\n");   Console.ReadLine(); } }   class SomeClass { public event EventHandler SomeEvent; public void OnSomeEvent() { if (SomeEvent != null) { var eventListeners = SomeEvent.GetInvocationList();   Console.WriteLine("Control: Raising Event"); for (int index = 0; index < eventListeners.Count(); index++) {   var methodToInvoke = (EventHandler)eventListeners[index]; methodToInvoke.BeginInvoke(this, new ThreadEventArgs(index), null, null); } Console.WriteLine("Control: Done Raising all Events"); } }   public class ThreadEventArgs : EventArgs { public ThreadEventArgs(int ThreadNumber) { this.ThreadNumber = ThreadNumber; } public int ThreadNumber { get; set; } }     } }  
«Делегаты, анонимные функции, события и лямбда-выражения» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Найди решение своей задачи среди 1 000 000 ответов
Найти
Найди решение своей задачи среди 1 000 000 ответов
Крупнейшая русскоязычная библиотека студенческих решенных задач

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot