Опубликован: 25.03.2010 | Доступ: свободный | Студентов: 1447 / 158 | Оценка: 4.31 / 4.00 | Длительность: 25:42:00
Лекция 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. Ввожу код на сайте, пишет:

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

 

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