Опубликован: 25.03.2010 | Доступ: свободный | Студентов: 1447 / 157 | Оценка: 4.31 / 4.00 | Длительность: 25:42:00
Лекция 10:

Интерфейсы, делегаты, события в C#

Создание событий с контролем адресатов

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

Если мы хотим как-то повлиять на событие библиотечного класса, то нужно помнить, что событие объявляется общедоступными и может наследоваться как обычный член класса. Это дает нам право расширить библиотечный класс, в котором можно выполнить какие-то действия: скрыть событие, объявив его заново с ключевым словом new как закрытое, или переопределить диспетчер события. Диспетчер в библиотечных классах обычно объявляется виртуальным и может быть переопределен в производном классе или даже совсем скрыт для следующих потомков его переобъявлением.

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

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

Синтаксис создания события расширенным способом сильно напоминает создание в классе свойства. Он также основывается на закрытом базовом поле и также обертывает это поле общедоступным членом. Только вместо автоматически вызываемых аксессоров get и set используются ключевые слова add и remove, а вместо базового поля используется закрытое поле-делегат. Методы add и remove автоматически вызываются при добавлении обработчика в список делегата или удаления его из списка.

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

using System;
    
namespace Test
{
    // Объявление делегата как типа 
    delegate void Message(object sender, string message);
    
    // Класс-источник сообщения
    class SourceMessage
    {
        ////////////////////////////////////////////
        // Это был стандартный способ
        ////////////////////////////////////////////
        // public event Message Mail;
    
        ////////////////////////////////////////////
        // Это расширенный способ
        ////////////////////////////////////////////
        // Объявление внутреннего поля как экземпляра делегата
        private Message mail;
    
        // Создание события с контролем адресатов
        public event Message Mail
        {
            add     // Контролируем добавление обработчиков в список
            {
                // Имя получателя сообщения
                string targetName = value.Target.GetType().Name;
    
                // Выявляем того, кого не любим!
                if (targetName == "BadClass")
                    // Ласково уведомляем 
                    Console.WriteLine("Типу BadClass 
      доступ к событию запрещен!\n");
                else
                    mail += value;
            }
            remove  // Удаление контролировать не будем. 
         //"Его там не стояло!" 
            {
                mail -= value;
            }
        }
    
        /////////////////////////////////////////////
        // Все остальное то-же самое, кроме замены
        // события Mail на закрытое поле-делегат mail
        /////////////////////////////////////////////
        // Метод диспетчеризации события. Объявили виртуальным 
        // и защищенным для возможности замещения в наследниках
        protected virtual void OnMail(string mess)
        {
            // Инициируем рассылку сообщения всем, 
            // кто подписался на событие
            // Здесь используется поле-делегат, внутри можно (и нужно!)
            if (mail != null)       // Если не пустой делегат
                mail(this, mess);   // Инициируем событие
        }
    
        // Объявляем и инициируем внутреннее поле базовым сообщением
        string message = "Сообщаю, что сработал таймер!!!\n"
            + "Текущее время ";
    
        // Объявляем внутреннее поле для видимости в методах
        System.Timers.Timer timer;
    
        // Конструктор класса-источника сообщения
        public SourceMessage()
        {
            // Создаем и запускаем таймер, который по истечении 
            // заданного времени инициирует рассылку сообщения
            timer = new System.Timers.Timer();
            timer.Interval = 5000d; // Сработает через 5 секунд
            timer.Elapsed += new System.Timers.ElapsedEventHandler
    (timer_Elapsed);
            timer.Start();      // Запускаем таймер
        }
    
        // Инициирование события из внешнего источника
        void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            // Извлекаем текущее системное время
            DateTime curTime = DateTime.Now;
            // Дополняем базовое сообщение временем
            message += curTime.ToString();
    
            OnMail(message);    // Вызываем метод диспетчеризации
            timer.Stop();       // Останавливаем системный таймер
        }
    }
    
    // Хороший класс-получатель сообщения
    class GoodClass
    {
        public GoodClass()
        {
            // Создаем объект с событием
            SourceMessage obj = new SourceMessage();
    
            // Подписываем на событие обработчик,
            // печатающий полученную информацию
            obj.Mail += new Message(obj_Mail);
        }
    
        // Обработчик
        void obj_Mail(object sender, string message)
        {
            String str = this.GetType().Name
                + " получил информацию от "
                + sender.GetType().Name + "\n"
                + "следующего содержания:\n\"" 
     + message + "\"";
            Console.WriteLine(str);
        }
    }
    
    // Плохой класс, которому не разрешено получать сообщения
    class BadClass
    {
        public BadClass()
        {
            // Создаем объект с событием
            SourceMessage obj = new SourceMessage();
    
            // Подписываемся на событие в благостном неведении,
            // что мы попали в черный список и нас
            // не включат в список рассылки
            obj.Mail += new Message(obj_Mail);
        }
    
        // Обработчик
        void obj_Mail(object sender, string message)
        {
            String str = "Получена информация от класса "
                + sender.GetType().Name + ":\n"
                + "\"" + message + "\"";
            Console.WriteLine(str);
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = "Расширенное создание 
    события (через 5 секунд!)";
    
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 47;
            Console.WindowHeight = 7;
    
            new GoodClass();// Исполняем хороший класс
            new BadClass(); // Исполняем плохой класс
    
            Console.ReadLine();
        }
    }
}
Листинг 10.20 . Создание события с контролирующим подписку кодом

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

Создание событий со списком делегатов

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

За событием закрепляется определенный ключ и при манипуляции с событием во внешнем коде делегаты, маркированные этим ключом, добавляются в список. Чтобы активизировать событие, достаточно обратиться к списку с закрепленным за событием ключом, все одноадресные делегаты будут извлечены из списка и присоединенные обработчики будут выполнены.

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

using System;
    
namespace Test
{
    // Класс-источник сообщения
    class SourceMessage
    {
        // Создание списка делегатов вместо базовых полей
        System.ComponentModel.EventHandlerList eventList
            = new System.ComponentModel.EventHandlerList();
    
        // Объявление типов делегатов
        public delegate void Message1();
        public delegate int Message2(string message);
        // Непустое возвращаемое значение
        public delegate void Message3(object sender, string message);
    
        // Создание ключей для делегатов
        Object key1 = new Object();
        Object key2 = new Object();
        Object key3 = new Object();
    
        // Создание события на базе списка
        public event Message1 Mail1
        {
            add
            {
                eventList.AddHandler(key1, value);// Дополняем список делегатов
            }
            remove
            {
                eventList.RemoveHandler(key1, value);// Удаляем из списка
            }
        }
    
        // Создание события на базе списка
        public event Message2 Mail2
        {
            add
            {
                eventList.AddHandler(key2, value);// Расширяем список делегатов
            }
            remove
            {
                eventList.RemoveHandler(key2, value);// Удаляем из списка
            }
        }
    
        // Создание события на базе списка
        public event Message3 Mail3
        {
            add
            {
                eventList.AddHandler(key3, value);// Расширяем список делегатов
            }
            remove
            {
                eventList.RemoveHandler(key3, value);// Удаляем из списка
            }
        }
    
        // Симуляция срабатывания события Mail1
        public void DispatchMail1()
        {
            // Извекаем из списка все делегаты для Mail1, помеченные ключом
            Message1 mail1 = (Message1)eventList[key1];
            if (mail1 != null)
                mail1();
        }
    
        // Симуляция срабатывания события Mail2
        public void DispatchMail2()
        {
            // Извекаем из списка все делегаты для Mail2, помеченные ключом
            Message2 mail2 = (Message2)eventList[key2];
            if (mail2 != null)
                mail2("\"mail2 из SourceMessage\"");
        }
    
        // Симуляция срабатывания события Mail3
        public void DispatchMail3()
        {
            // Извекаем из списка все делегаты для Mail3, помеченные ключом
            Message3 mail3 = (Message3)eventList[key3];
            if (mail3 != null)
                mail3(this, "\"mail3 из SourceMessage\"");
        }
    }
    
    // Получатель сообщения
    class MyClass
    {
        // Конструктор
        public MyClass()
        {
            // Создаем объект с событиями
            SourceMessage obj = new SourceMessage();
    
            // Подписываемся на обработчики
            obj.Mail1 += new SourceMessage.Message1(obj_Mail1);
            obj.Mail1 += new SourceMessage.Message1(obj_Mail1);
            obj.Mail2 += new SourceMessage.Message2(obj_Mail2);
            obj.Mail3 += new SourceMessage.Message3(obj_Mail3);
    
            // Запускаем события
            obj.DispatchMail1();
            obj.DispatchMail2();
            obj.DispatchMail3();
        }
    
        // Обработчики
        void obj_Mail1()
        {
            Console.WriteLine("Обработчик события Mail1.");
        }
    
        int obj_Mail2(string message)
        {
            Console.WriteLine("\nОбработчик события Mail2.\n" +
                "Сообщение: {0}\n", message);
            return 1;
        }
    
        void obj_Mail3(object sender, string message)
        {
            Console.WriteLine("Обработчик события Mail3.\n" +
                "Сообщение: {0}", message);
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = "Применение списка делегатов";
    
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 36;
            Console.WindowHeight = 9;
    
            new MyClass();// Исполняем
    
            Console.ReadLine();
        }
    }
}
Листинг 10.21 . Создание событий со списком делегатов

Максим Филатов
Максим Филатов

Прошел курс. Получил код Dreamspark. Ввожу код на сайте, пишет:

Срок действия этого кода проверки уже истек. Проверьте, правильно ли введен код. У вас осталось две попытки. Вы также можете выбрать другой способ проверки или предоставить соответствующие документы, подтверждающие ваш академический статус.

 

Как активировать код?