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

Создание оконных приложений Windows Forms

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

Делегаты являются типом, реализующим механизм динамического вызова функций с разными именами, но одинаковой сигнатурой и типом возвращаемого значения. Это еще один подход к полиморфизму в объектно-ориентированном программировании. Делегат аналогичен указателю на функцию в C / C++.

Делегат объявляется как тип в области видимости пространства имен с помощью ключевого слова delegate по следующему синтаксису

delegate тип_возврата имя_типа_делегата(список_параметров);

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

имя_типа_делегата объект = new конструктор_делегата(имя_метода);

После создания экземпляра делегата для конкретного метода вызовы метода можно выполнять через объект делегата

объект_делегата(список_параметров);

вместо

имя_метода(список_параметров);

Все делегаты представляют собой типы, которые неявным образом выводятся из класса System.Delegate, но напрямую так не объявляются.

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

using System;
using System.Windows.Forms;
    
namespace MyApp
{
    // Объявление своего типа класса
    class MyClass
    {
        string str;
    
        public void Add(int x, int y)
        {
            str = String.Format("{0} + {1} = {2}", x, y, x + y);
            MessageBox.Show(str, "Сложение целых чисел");
        }
    
        public void Subtract(int x, int y)
        {
            str = String.Format("{0} - {1} = {2}", x, y, x - y);
            MessageBox.Show(str, "Вычитание целых чисел");
        }
    }
}
Листинг 6.22 . Код файла MyClass.cs

Теперь создадим вызывающую сторону кода класса, в которой предусмотрим

  1. Вызов методов напрямую
  2. Одиночные вызовы через экземпляры делегата
  3. Организацию цепочки вызовов (многоадресный вызов) через объект делегата

Вот этот код

// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
    // Объявление своего типа делегата
    delegate void Arithmetics(int a, int b);
    
    class EntryPoint
    {
        static void Main()
        {
            // Объявляем ссылочную переменную типа делегата
            Arithmetics obDelegate;
            // Объявляем ссылочную переменную типа класса
            MyClass ob;
            // Создаем объект класса с методами
            ob = new MyClass();
    
            // Заготавливаем передаваемые методам аргументы
            int x = 5, y = 10;
    
            // Прямой вызов методов объекта класса
            ob.Add(x, y);
            ob.Subtract(x, y);
    
            // Косвенный (через делегата) вызов методов объекта класса
            // Адресуемся к первому методу объекта класса
            obDelegate = new Arithmetics(ob.Add);
            obDelegate(x, y);   // Вызываем метод через объект делегата
            // Адресуемся ко второму методу объекта класса
            obDelegate = new Arithmetics(ob.Subtract);
            obDelegate(x, y);   // Вызываем метод через объект делегата
    
            // Использование делегата для организации цепочки вызовов
            obDelegate = new Arithmetics(ob.Add);
            obDelegate += new Arithmetics(ob.Subtract);
            obDelegate(x, y);   // Вызов методов
        }
    }
}
Листинг 6.23 . Код файла Program.cs

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

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

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

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

В отличие от делегатов, которые являются типами и объявляются непосредственно в пространстве имен, события являются членами классов и объявляются внутри классов как экземпляры делегатов с ключевым словом event. Чтобы событие было видно в других классах, оно должно быть общедоступным ( public ) или защищенным ( protected ). События как члены класса могут наследоваться другими классами точно также, как и другие доступные члены класса (методы, свойства, поля).

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

Вот код примера

using System;
using System.Windows.Forms;
using System.Drawing;
    
namespace MyApp
{
    // Объявление с помощью делегата прототипов событий и обработчиков
    delegate void Premium(Driver driver, int speed);
    delegate void Penalty(Driver driver, int speed);
    
    // Гаишник
    class Gai
    {
        // Объявление событий и критерия оценки
        public static event Premium premium;
        public static event Penalty penalty;
        const int SPEED_BORDER = 60;    // Критерий оценки
    
        // Измерить скорость и сгенерировать событие
        public static void MeasureSpeed(Driver driver, int speed)
        {
            // Критерий выполнен и обработчик существует
            if (speed <= SPEED_BORDER && premium != null)
                premium(driver, speed);
            else if(penalty != null)
                penalty(driver, speed);
        }
    }
    
    // Писарь
    //class Писарь
    class Clerk
    {
        // Вспомогательное поле
        string message;
    
        // Конструктор для автоматической подписки
        //public Писарь()
        public Clerk()
        {
            // Пописка на события
            Gai.penalty += new Penalty(Gai_penalty);
            Gai.premium += new Premium(Gai_premium);
        }
    
        void Gai_penalty(Driver driver, int speed)
        {
            message = "Оштрафовать водителя ";
            message += driver.Name + "а за скорость ";
            message += speed + " км/час";
            MessageBox.Show(message, "Писарь");
        }
    
        void Gai_premium(Driver driver, int speed)
        {
            message = "Поощрить водителя ";
            message += driver.Name + "а за скорость ";
            message += speed + " км/час";
            MessageBox.Show(message, "Писарь");
        }
    }
    
    // Кассир
    class Cashier
    {
        // Вспомогательное поле
        string message;
    
        // Конструктор для автоматической подписки
        public Cashier()
        {
            Gai.penalty += new Penalty(Gai_penalty);
            Gai.premium += new Premium(Gai_premium);
        }
    
        void Gai_penalty(Driver driver, int speed)
        {
            message = "Взымаю штраф с водителя ";
            message += driver.Name + "а за скорость ";
            message += speed + " км/час";
            MessageBox.Show(message, "Кассир");
        }
    
        void Gai_premium(Driver driver, int speed)
        {
            message = "Выдаю премию водителю ";
            message += driver.Name + "у за скорость ";
            message += speed + " км/час";
            MessageBox.Show(message, "Кассир");
        }
    }
    
    // Водитель
    class Driver
    {
        public string Name;
    
        public Driver(string Name)
        {
            Gai.penalty += new Penalty(Gai_penalty);
            Gai.premium += new Premium(Gai_premium);
            this.Name = Name;
        }
    
        void Gai_penalty(Driver driver, int speed)
        {
            if (driver == this)
                MessageBox.Show("Признателен за штраф!!!", "Водитель " + Name);
        }
    
        void Gai_premium(Driver driver, int speed)
        {
            if (driver == this)
                MessageBox.Show("Благодарю за премию!!!", "Водитель " + Name);
        }
    }
    
    // Для запуска
    class ExecuteClass
    {
        public ExecuteClass()
        {
            // Создаем объекты
            //Писарь писарь = new Писарь();
            // Если событие принимают сразу несколько объектов,
            // то порядок обработки событий определяется
            // порядком создания принимающих объектов!!!
            Clerk clerk = new Clerk();
            Cashier cashier = new Cashier();
            Driver driver1 = new Driver("Иванов");
            Driver driver2 = new Driver("Петров");
            Driver driver3 = new Driver("Сидоров");
    
            // Измерить скорость и передать объектам
            // готовое решение с дополнительной информацией
            Gai.MeasureSpeed(driver1, 50);
            Gai.MeasureSpeed(driver2, 70);
            Gai.MeasureSpeed(driver3, 90);
        }
    }
}
Листинг 6.24 . Код файла AppClasses.cs
// Удалили сокращения пространств имен
// Все используемые типы находятся в проекте
namespace MyApp
{
    class EntryPoint
    {
        static void Main()
        {
            new ExecuteClass();
        }
    }
}
Листинг 6.25 . Код файла Program.cs

Обратите внимание на закомментированное имя типа "Писарь" и ссылку "писарь". Их можно вполне использовать как идентификаторы переменных и пользовательских типов. Visual Studio 2005 это позволяет, но сообщество программистов не приветствует - непрофессионально!

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

Здесь есть одно обстоятельство. Если объект сгенерировал событие, но к нему не было присоединено ни одного обработчика, то будет сгенерировано исключение. Поэтому мы сделали предварительную проверку ссылки события на нулевое значения.

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

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

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

 

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