Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 4809 / 630 | Оценка: 3.97 / 3.80 | Длительность: 14:45:00
Лекция 10:

Делегаты и события

< Лекция 9 || Лекция 10: 1234

События

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

Механизм событий можно также описать с помощью модели "публикация — подписка": один класс, являющийся отправителем (sender) сообщения, публикует события, которые он может инициировать, а другие классы, являющиеся получателями (receivers) сообщения, подписываются на получение этих событий.

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

  • описание делегата, задающего сигнатуру обработчиков событий;
  • описание события;
  • описание метода (методов), инициирующих событие.

Синтаксис события похож на синтаксис делегата:

[ атрибуты ] [ спецификаторы ] event тип имя_события

Для событий применяются спецификаторы new, public, protected, internal, private, static, virtual, sealed, override, abstract и extern. Например, так же как и методы, событие может быть статическим ( static ), тогда оно связано с классом в целом, или обычным — в этом случае оно связано с экземпляром класса. Тип события — это тип делегата, на котором основано событие.

Пример описания делегата и соответствующего ему события:

public delegate void Del( object o );                 // объявление делегата
class A
{
    public event Del Oops;                             // объявление события
    ...
}

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

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

В листинге 10.4 приведен код из листинга 10.2, переработанный с использованием событий, а рисунок 10.2 поясняет организацию обработки событий.

Выполнение программы с двумя нулевыми коэффициентами

Рис. 10.2. Выполнение программы с двумя нулевыми коэффициентами
using System;
namespace ConsoleApplication1
{
    public delegate void Del();                       // объявление делегата

    class Subj                                             // класс-источник
    {
        public event Del Oops;                         // объявление события

        public void CryOops()                 // метод, инициирующий событие
        {
            Console.WriteLine( "ОЙ!" );
            if ( Oops != null ) Oops();
        }
    }

    class ObsA                                          // класс-наблюдатель
    {
        public void Do();                    // реакция на событие источника
        {
            Console.WriteLine( "Бедняжка!" );
        }
    }

    class ObsB                                          // класс-наблюдатель
    {
        public static void See()             // реакция на событие источника
        {
            Console.WriteLine( "Да ну, ерунда!" );
        }
    }

    class Class1
    {
        static void Main()
        {
            Subj s  = new Subj();              //    объект класса-источника

            ObsA o1 = new ObsA();              //                    объекты
            ObsA o2 = new ObsA();              //         класса-наблюдателя

            s.Oops += new Del( o1.Do );        //                 добавление
            s.Oops += new Del( o2.Do );        //               обработчиков
            s.Oops += new Del( ObsB.See );     //                  к событию

            s.CryOops();                       //      инициирование события 
        }
    }
}
Листинг 10.4. Оповещение наблюдателей с помощью событий

Внешний код может работать с событиями единственным образом: добавлять обработчики в список или удалять их, поскольку вне класса могут использоваться только операции += и -=. Тип результата этих операций — void, в отличие от операций сложного присваивания для арифметических типов.

Внутри класса, в котором описано событие, с ним можно обращаться, как с обычным полем, имеющим тип делегата: использовать операции отношения, присваивания и т. д. Значение события по умолчанию — null.

В библиотеке .NET описано огромное количество стандартных делегатов, предназначенных для реализации механизма обработки событий. Большинство этих классов оформлено по одним и тем же правилам:

  • имя делегата заканчивается суффиксом EventHandler ;
  • делегат получает два параметра:
    • первый параметр задает источник события и имеет тип object ;
    • второй параметр задает аргументы события и имеет тип EventArgs или производный от него.

Если обработчикам события требуется специфическая информация о событии, то для этого создают класс, производный от стандартного класса EventArgs, и добавляют в него необходимую информацию. Если делегат не использует такую информацию, можно обойтись стандартным классом делегата System.EventHandler.

Имя обработчика события принято составлять из префикса On и имени события. В листинге 10.5 приведен пример из листинга 10.4, оформленный в соответствии со стандартными соглашениями .NET.

using System;
namespace ConsoleApplication1
{
    class Subj
    {
        public event EventHandler Oops;

        public void CryOops()
        {
            Console.WriteLine( "ОЙ!" );
            if ( Oops != null ) Oops( this, null );
        }
    }

    class ObsA
    {
        public void OnOops( object sender, EventArgs e )
        {
            Console.WriteLine( "Бедняжка!" );
        }
    }

    class ObsB
    {
        public static void OnOops( object sender, EventArgs e )
        {
            Console.WriteLine( "Да ну, ерунда!" );
        }
    }

    class Class1
    {   static void Main()
        {
            Subj s  = new Subj();

            ObsA o1 = new ObsA();
            ObsA o2 = new ObsA();

            s.Oops += new EventHandler( o1.OnOops );
            s.Oops += new EventHandler( o2.OnOops );
            s.Oops += new EventHandler( ObsB.OnOops );

            s.CryOops();
        }
    }
}
Листинг 10.5. Использование стандартного делегата EventHandler

Те, кто работает с C# версии начиная с 2.0, могут упростить эту программу, используя возможность неявного создания делегатов при регистрации обработчиков событий. Об этом можно прочитать в учебнике [4].

< Лекция 9 || Лекция 10: 1234
Степан Боженко
Степан Боженко
Россия, г.Тула