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

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

Делегаты

Делегат, это тип, задающий сигнатуру методов и обеспечивающий механизм их обобщенного вызова. Вначале объявляется сам делегат как тип и сразу же задается прототип функций, которые в будущем могут адресоваться экземплярами этого типа. Затем объявляется ссылка типа делегата, которая будет указывать на коллекцию экземпляров делегата. И наконец, создаются сами объекты делегата, каждый из которых адресует одну функцию, и комбинируются в коллекцию, на которую указывает объявленная ссылка. Коллекция представляет собой список адресуемых делегатом функций и поддерживается операциями += и -= (или статическими методами Delegate.Combine() и Delegate.Remove() ).

Экземпляр делегата способен накапливать в себе ссылки на методы объявленной сигнатуры и вызывать эти методы либо по одному, либо сразу весь список.

Одноадресная работа делегатов

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

Область видимости делегата можно регулировать точно так же, как и область видимости любого другого типа в приложении.

using System;
    
namespace Test
{
    class DelegateTest
    {
        // Объявляем функции с сигнатурой делегатов
        public void Show1()
        {
            Console.WriteLine("Вызов: void Show1()");
        }
    
        // Объявляем функции с сигнатурой делегатов
        public void Show2()
        {
            Console.WriteLine("Вызов: void Show2()");
        }
    
        // Объявляем функции с сигнатурой делегатов
        public int Draw1(string str1)
        {
            Console.WriteLine("Вызов: {0}", str1);
            return 1;
        }
    
        // Объявляем функции с сигнатурой делегатов
        public int Draw2(string str2)
        {
            Console.WriteLine("Вызов: {0}", str2);
            return 2;
        }
    
        // Объявляем статическую функцию
        public static int Print(string str)
        {
            Console.WriteLine("Вызов: {0}", str);
            return 0;
        }
    }
       
    // Объявляем делегат в пространстве имен
    delegate void TypeShow();
    
    // Вызывающая сторона
    class MyClass
    {
        // Объявляем делегат в классе
        delegate int TypeDraw(string str);
    
        public MyClass()
        {
            // Создаем экземпляр класса с методами
            DelegateTest delegateTest = new DelegateTest();
    
            // Объявляем ссылки на объекты делегатов
            TypeShow typeShow;
            TypeDraw typeDraw;
    
            // Создаем объекты делегатов
            typeShow = new TypeShow(delegateTest.Show1);
            typeDraw = new TypeDraw(delegateTest.Draw1);
    
            // Вызываем методы посредством делегатов
            typeShow();
            typeDraw("int Draw1(string str1)");
    
            // Адресуемся к другим методам с той же сигнатурой
            typeShow = new TypeShow(delegateTest.Show2);
            typeDraw = new TypeDraw(delegateTest.Draw2);
    
            // Вызываем другие методы посредством делегатов
            typeShow();
            typeDraw("int Draw2(string str2)");
    
            // Вызываем статический метод 
            // посредством подходящего делегата 
            typeDraw = new TypeDraw(DelegateTest.Print);
            typeDraw("static int Print(string str)");
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = "Применение делегатов";
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 40;
            Console.WindowHeight = 7;
    
            new MyClass();// Чтобы сработал конструктор
    
            Console.ReadLine();
        }
    }
}
Листинг 10.8 . Одноадресная работа делегатов

Делегат может адресовать как методы объекта (экземпляра класса), так и статические методы. Главное, чтобы заголовки объявления делегата и объявления метода совпадали как по типу возвращаемого значения, так и по сигнатуре.

Многоадресная работа делегатов

Для методов, имеющих одинаковую сигнатуру и не возвращающих значение (только с void ) с помощью делегата можно организовать сразу цепочку вызовов.

using System;
    
namespace Test
{
    class MultiTest
    {
        // Статическое поле
        static int x = 1;
    
        // Объявляем функции с одинаковой сигнатурой
        public void Handler1(string name)
        {
            Console.WriteLine(name + x++);
        }
    
        public void Handler2(string name)
        {
            Console.WriteLine(name + x++);
        }
    
        public void Handler3(string name)
        {
            Console.WriteLine(name + x++);
        }
    }
    
    // Вызывающая сторона
    class MyClass
    {
        // Объявляем делегат в классе
        delegate void TypeHandler(string text);
    
        public MyClass()
        {
            // Создаем экземпляр класса с методами
            MultiTest obj = new MultiTest();
    
            // Создаем объект-делегат и заполняем адресами
            TypeHandler Handler = new TypeHandler(obj.Handler1);
            Handler += new TypeHandler(obj.Handler2);
            Handler += obj.Handler3;    // Упрощенный синтаксис
    
            // Вызываем цепочку методов
            Handler("Вызов: Handler");
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = "Многоадресные делегаты";
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 30;
            Console.WindowHeight = 10;
    
            new MyClass();// Чтобы сработал конструктор
    
            Console.ReadLine();
        }
    }
}
Листинг 10.9 . Многоадресная работа делегата

Делегаты являются экземплярами типа System.MulticastDelegate, который в свою очередь наследует абстрактный класс System.Delegate. Экземпляр типа MulticastDelegate может хранить в себе одну или сразу несколько ссылок на методы. В любом случае мы не можем явно объявлять делегат с помощью типа MulticastDelegate. Это делается с помощью ключевого слова delegate, но неявно порождается объект класса MulticastDelegate. Если посмотреть на структуру последнего примера через панель Class View, то это становится очевидным.


Пустые делегаты

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

using System;
    
namespace Test
{
    class MultiTest
    {
        // Объявляем функции одного прототипа
        public void Handler1()
        {
            Console.WriteLine("Исполняется Handler1");
        }
    
        public void Handler2()
        {
            Console.WriteLine("Исполняется Handler2");
        }
    
        public void Handler3()
        {
            Console.WriteLine("Исполняется Handler3");
        }
    }
    
    // Вызывающая сторона
    class MyClass
    {
        // Для заголовка консольного окна
        public static string Title = "Пустые делегаты";
    
        // Объявляем делегат как член класса
        delegate void MyDelegate();
    
        public MyClass()
        {
            // Создаем экземпляр класса с методами
            MultiTest obj = new MultiTest();
    
            // Создаем объект-делегат и заполняем ссылками на функции
            // Инициализируем первой ссылкой
            MyDelegate del = new MyDelegate(obj.Handler1);
            // Добавляем другие ссылки
            del += obj.Handler2;
            del += obj.Handler3;
    
            // Вызываем цепочку методов
            del();
            Console.WriteLine();
    
            // Извлекаем метод и опять вызываем цепочку методов
            del -= obj.Handler1;
            del();
            Console.WriteLine();
    
            // Извлекаем метод и опять вызываем цепочку методов
            del = (MyDelegate)Delegate.Remove(del, new MyDelegate(obj.Handler2));
            del();
            Console.WriteLine();
    
            // Извлекаем последний метод и пытаемся адресоваться к пустому делегату
            del -= obj.Handler3;
            //del(); // Будет выброшено исключение, нужно проверять!!!
            if (del != null)
            {
                del(); // Так все нормально!
            }
            else
                Console.WriteLine("Список делегата исчерпан!");
        }
    }
    
    // Запуск
    class Program
    {
        static void Main()
        {
            // Настройка консоли
            Console.Title = MyClass.Title;
            Console.ForegroundColor = ConsoleColor.White;
            Console.CursorVisible = false;
            Console.WindowWidth = 30;
            Console.WindowHeight = 11;
    
            new MyClass();// Исполняем
    
            Console.ReadLine();
        }
    }
}
Листинг 10.10 . Контроль за возможностью пустого списка делегата

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

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

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

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

 

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