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

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

< Лекция 9 || Лекция 10: 1234
Аннотация: Назначение, описание и использование делегатов. Паттерн "наблюдатель". Механизм событий. Введение в многопоточные приложения. Асинхронные делегаты.

Делегаты

Презентацию к данной лекции Вы можете скачать здесь.

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

Описание делегатов

Описание делегата задает сигнатуру методов, которые могут быть вызваны с его помощью:

[ атрибуты ] [ спецификаторы ] delegate тип имя_делегата ( [ параметры ] )

Допускаются спецификаторы new, public, protected, internal и private. Тип описывает возвращаемое значение методов, вызываемых с помощью делегата, а необязательными параметрами делегата являются параметры этих методов. Делегат может хранить ссылки на несколько методов и вызывать их поочередно; естественно, что сигнатуры всех методов должны совпадать.

Пример описания делегата:

public delegate void D ( int i );

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

Использование делегатов

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

Делегаты применяются в основном для следующих целей:

  • получения возможности определять вызываемый метод не при компиляции, а во время выполнения программы;
  • обеспечения связи между объектами по типу "источник — наблюдатель";
  • создания универсальных методов, в которые можно передавать другие методы;
  • поддержки механизма обратных вызовов.

Рассмотрим сначала пример реализации первой из этих целей. В листинге 10.1 объявляется делегат, с помощью которого один и тот же оператор используется для вызова двух разных методов ( C00l и Hack ).

using System;
namespace ConsoleApplication1
{
    delegate void Del ( ref string s );               // объявление делегата

    class Class1
    {
        public static void C00l ( ref string s )                  // метод 1
        {
            string temp = "";
            for ( int i = 0; i < s.Length; ++i )
            {
                if      ( s[i] == 'o' || s[i] == 'O') temp += '0';
                else if ( s[i] == 'l' )               temp += '1';
                else                                  temp += s[i];
            }
            s = temp;
        }

        public static void Hack ( ref string s )                  // метод 2
        {
            string temp = "";
            for ( int i = 0; i < s.Length; ++i )
                if ( i / 2 * 2 == i ) temp += char.ToUpper( s[i] );
                else                  temp += s[i];

            s = temp; 
        }

        static void Main()
        {
            string s = "cool hackers";
            Del d;                                     // экземпляр делегата

            for ( int i = 0; i < 2; ++i )
            {
                d = new Del( C00l );              // инициализация методом 1
                if ( i == 1 ) d = new Del(Hack);  // инициализация методом 2

                d( ref s );     // использование делегата для вызова методов
                Console.WriteLine( s );
            }
        }
    }
}
Листинг 10.1. Простейшее использование делегата

Результат работы программы:

c001 hackers
C001 hAcKeRs

Использование делегата имеет тот же синтаксис, что и вызов метода. Если делегат хранит ссылки на несколько методов, они вызываются последовательно в том порядке, в котором были добавлены в делегат.

Добавление метода в список выполняется либо с помощью метода Combine, унаследованного от класса System.Delegate, либо, что удобнее, с помощью перегруженной операции сложения. Вот как выглядит измененный метод Main из предыдущего листинга, в котором одним вызовом делегата выполняется преобразование исходной строки сразу двумя методами.

static void Main()
{
        string s = "cool hackers";
        Del d = new Del( C00l );
        d += new Del( Hack );             // добавление метода в делегат

        d( ref s );
        Console.WriteLine( s );           // результат: C001 hAcKeRs
}

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

  • сигнатура методов должна в точности соответствовать делегату;
  • методы могут быть как статическими, так и обычными методами класса;
  • каждому методу в списке передается один и тот же набор параметров;
  • если параметр передается по ссылке, изменения параметра в одном методе отразятся на его значении при вызове следующего метода;
  • если параметр передается с ключевым словом out или метод возвращает значение, результатом выполнения делегата является значение, сформированное последним из методов списка (в связи с этим рекомендуется формировать списки только из делегатов, имеющих возвращаемое значение типа void );
  • если в процессе работы метода возникло исключение, не обработанное в том же методе, последующие методы в списке не выполняются, а происходит поиск обработчиков в объемлющих делегат блоках;
  • попытка вызвать делегат, в списке которого нет ни одного метода, вызывает генерацию исключения System.NullReferenceException.

Паттерн "наблюдатель"

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

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

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

Наблюдатель (observer) определяет между объектами зависимость типа "один ко многим", так что при изменении состояния одного объекта все зависящие от него объекты получают извещение и автоматически обновляются. Рассмотрим пример (листинг 10.2), в котором демонстрируется схема оповещения источником трех наблюдателей. Гипотетическое изменение состояния объекта моделируется сообщением "ОЙ!". Один из методов в демонстрационных целях сделан статическим.

using System;
namespace ConsoleApplication1
{
    public delegate void Del( object o );             // объявление делегата

    class Subj                                             // класс-источник
    {
        Del dels;                          // объявление экземпляра делегата

        public void Register( Del d )                // регистрация делегата
        {
            dels += d;
        }

        public void OOPS()                               // что-то произошло
        {
            Console.WriteLine( "ОЙ!" );
            if ( dels != null ) dels( this );     // оповещение наблюдателей
        }
    }

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

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

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

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

            s.Register( new Del( o1.Do ) );      //      регистрация методов
            s.Register( new Del( o2.Do ) );      // наблюдателей в источнике
            s.Register( new Del( ObsB.See ) );   //  ( экземпляры делегата )

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

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

Результат работы программы:

ОЙ!
Бедняжка!
Бедняжка!
Да ну, ерунда!

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

Связь "источник — наблюдатель" устанавливается во время выполнения программы для каждого объекта по отдельности. Если наблюдатель больше не хочет получать уведомления от источника, можно удалить соответствующий метод из списка делегата с помощью метода Remove или перегруженной операции вычитания, например:

public void UnRegister( Del d )                 // удаление делегата
{
    dels -= d;
}
< Лекция 9 || Лекция 10: 1234
Лариса Волосникова
Лариса Волосникова
Россия
Игорь Лисицын
Игорь Лисицын
Россия