Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 5596 / 979 | Оценка: 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
Георгий Кузнецов
Георгий Кузнецов

"Сокрытие деталей реализации называется инкапсуляцией (от слова "капсула"). "

Сколько можно объяснять?!

ИНКАПСУЛЯЦИЯ НЕ РАВНА СОКРЫТИЮ!!!

Инкапсуляция это парадигма ООП, которая ОБЕСПЕЧИВАЕТ СОКРЫТИЕ!!!

НО СОКРЫТИЕМ  НЕ ЯВЛЯЕТСЯ!!! 

Если буровая коронка обеспечивает разрушение породы, то является ли она сама разрушением породы? Конечно нет!

Ольга Притоманова
Ольга Притоманова