Опубликован: 25.03.2010 | Уровень: для всех | Доступ: платный
Лекция 10:

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

Превращение делегата в событие

А надо бы их различить, поскольку они предназначены совершенного для разных целей и имеют разные механизмы обработки. Чтобы внести ясность в этот вопрос, разработчики C# и библиотеки .NET Framework решили объявлять ссылку на объект делегата, когда он используеся для рассылки сообщений, с ключевым словом event и называть ее событием.

Остальной механизм работы такого делегата-события остался прежним, кроме одного: теперь событие-делегат является собственностью класса-источника и вызывать его напрямую из класса-приемника как поле сообщений нельзя. Событие должно инициироваться функцией-членом класса, отсылающего сообщение ( функция диспетчеризации ), в котором оно для этих целей и объявлено. В приемнике сообщения можно только подписаться на событие источника путем прикрепления нужных функций-обработчиков, а далее вызвать функцию диспетчеризации владельца события. Но по-прежнему наполнение списка события конкретными адресами функций выполняют объекты-делегаты.

Объявление в классе-источнике делегата как события означает только возможность на него подписаться функциям внешнего кода, но не гарантирует, что на него действительно кто-то подпишется в момент возбуждения этого события в классе-источнике. Поэтому функция диспетчеризации вначале проверяет, не пустой ли делегат, и только потом выполняет вызов связанных с ним методов-обработчиков.

В соответствии со сказанным изменим предыдущий пример совсем на немного (выделено в коде) и получим прежний результат, только теперь уже с участием события как члена класса

using System;
    
namespace Test
{
    // Образец сообщения определяется делегатом
    delegate void Message(string message);
    
    // Источник сообщения
    class SourceMessage
    {
        // Общедоступное поле ссылки на объект-делегат,
        // который теперь называется событием и наполняется
        // указателями на функции в классах-получателях 
        public event Message mail;
    
        // Необязательное поле с рассылаемым сообщение
        public string message;
    
        // Разослать сообщение - функция диспетчеризации
        public void DispatchMessage(string mess)
        {
            // Сохраняем внешнее сообщение во внутреннем поле
            message = mess;
    
            // Инициируем рассылку сообщения всем, 
            // кто зарегистрировался в объекте-делегате
            if (mail != null)   // Если не пустой делегат
                mail(mess);
        }
    }
    
    // Получатель сообщения
    class Addressee1
    {
        // Функции
        public void Handler(string message)
        {
            Console.WriteLine("Addressee1 получил:"
                + "\n\t\"{0}\"", message);
        }
    }
    
    // Получатель сообщения
    class Addressee2
    {
        // Функции
        public void Handler(string message)
        {
            Console.WriteLine("Addressee2 получил:"
                + "\n\t\"{0}\"", message);
        }
    }
    
    // Вызывающая сторона
    class MyClass
    {
        static public string Title = "Рассылка 
    сообщений событием";
    
        public MyClass()
        {
            // Создаем объекты источника и получателей сообщения
            SourceMessage source = new SourceMessage();
            Addressee1 obj1 = new Addressee1();
            Addressee2 obj2 = new Addressee2();
    
            // Формируем список обработчиков события с 
      //помощью объектов-делегатов
            source.mail += new Message(obj1.Handler);
            source.mail += new Message(obj2.Handler);
    
            // Рассылаем сообщение только через функцию-член источника
            //source.mail("Первое сообщение");
            source.DispatchMessage("Первое сообщение");
            Console.WriteLine();
    
            // Рассылаем сообщение через функцию диспетчеризации
            source.DispatchMessage("Второе сообщение");
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = MyClass.Title;
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 32;
            Console.WindowHeight = 10;
    
            new MyClass();// Исполняем
    
            Console.ReadLine();
        }
    }
}
Листинг 10.18 . Однонаправленная передача сообщений объектам с помощью события

Если теперь посмотреть на состав класса SourceMessage через панель Class View, то мы можем отличить переменную от события.


В последнем примере событие объявлено в одном классе, подписка обработчиков выполнена в другом классе, а сами обработчики находятся в третьих двух классах. Но, как правило, обработчики принадлежат тому классу, который содержит в себе экземпляр класса-источника события и в котором выполнена подписка на событие. Кроме того, инициация события выполняется внешними причинами в результате действий пользователя или возникновения каких-то программных условий. Чаще всего событие инициируется самой операционной системой. В нашем случае мы имитируем внешнюю причину, используя для этого нашу функцию диспетчеризации DispatchMessage().

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

Типичный способ создания событий

В соответствии с парадигмой объектно-ориентированного программирования событие должно передаваться объектом-источником при возникновении определенных обстоятельств. Другие классы, содержащие в себе объект с общедоступным полем-событием, по отношению к этому событию могут выступать только как клиенты (внешний код). Они могут только подписаться традиционным способом на событие и прослушивать его, а при получении - реагировать кодом прикрепленных к событию своих обработчиков. Согласуем код нашего последнего примера с изложенными соображениями

using System;
    
namespace Test
{
    // Образец сообщения определяется делегатом
    delegate void Message(object sender, string message);
    
    // Класс-источник сообщения
    class SourceMessage
    {
        // Общедоступное поле ссылки на событие
        public event Message 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 MyClass
    {
        static public string Title = 
            "Передача сообщения событием (через 5 секунд!)";
    
        public MyClass()
        {
            // Создаем объект с событием
            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 = MyClass.Title;
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 45;
            Console.WindowHeight = 4;
    
            new MyClass();// Исполняем
    
            Console.ReadLine();
        }
    }
}
Листинг 10.19 . Пример типичного создания событий

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

Модель программирования, основанная на событиях, сейчас является наиболее популярной, а для некоторых задач - и единственно возможной. Она наиболее приспособлена для построения интерактивных приложений, где требуется организовать диалог с пользователем. Когда пользователь не производит никаких действий, приложение находится в состоянии простоя ( idle - простой) и требует минимальных ресурсов компьютера. Только консольные приложения, многие из которых называют утилитами, выполняются непрерывно от начала до конца, и возможно не требуют применения событий так остро. Но и они для обмена информацией объектов приложения тоже могут использовать механизм событий, в чем мы только что убедились на последнем примере.

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

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

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

 

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

Денис Пашков
Денис Пашков
Россия
Татьяна Ковалюк
Татьяна Ковалюк
Украина, Киев, Киевский политехнический институт, 1974