Национальный исследовательский ядерный университет «МИФИ»
Опубликован: 19.08.2004 | Доступ: свободный | Студентов: 6055 / 401 | Оценка: 4.16 / 3.86 | Длительность: 10:50:00
Специальности: Программист

Лекция 12: Событийно управляемое программирование в .NET

Обсудив основные возможности описания и использования механизма делегатов в языке программирования C#, рассмотрим свойства делегатов как объектов языка более подробно. При этом будем приводить необходимые примеры на языке программирования C#.

Прежде всего, рассмотрим порядок описания языкового объекта-делегата. Проиллюстрируем обобщение создания переменной-делегата в виде формы Бэкуса-Наура (БНФ):

<описание_переменной-делегата>::=
    new <тип_делегата> 
      (<объект>.<метод>);

При этом переменная-делегат в ходе соотнесения инициализируется конкретным методом явно указанного объекта в соответствии с типом делегата. Необходимо также отметить, что в структуре переменной-делегата хранится не только сам метод, но и его выход или приемник (receiver). Тем не менее, переменная-делегат не имеет собственных параметров:

new Notifier(myObj.SayHello);

В данном фрагменте программного кода на языке C# приведен пример описания переменной-делегата как конкретизации типа-делегата Notifier в соотнесении с уже известным нам методом SayHello определенного пользователем объекта myObj. Заметим, что присутствующий в описании объект myObj cможет быть определен явно посредством описателя this, а может быть и опущен, как, например, в следующем фрагменте программы на языке C#:

new Notifier(SayHello);

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

При этом обобщенный формат описания переменной-делегата для языка программирования C# в виде БНФ примет вид:

<описание_переменной-делегата>::=
    new <тип_делегата> 
        (<класс>.<метод>)

Таким образом, описание переменной-делегата со статическим методом в форме кода на языке программирования C# может иметь, например, следующий вид:

new Notifier(MyClass.StaticSayHello);

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

Прежде всего, следует отметить, что метод в составе делегата не может быть описан как абстрактный ( abstract ), но может быть определен c использованием описателей как виртуальный ( virtual ), замещенный ( override ) или как экземпляр ( new ).

Кроме того, описание метода должно соответствовать описанию типа делегата, т.е. оба описания должны иметь одинаковое количество параметров и типы параметров (включая тип возвращаемого значения), причем виды параметров (в частности, с передачей по ссылке ( ref ), по значению ( value ), а также возвращаемые ( out )) должны совпадать.

Еще одним важным свойством переменных-делегатов является их динамическая природа. Теоретически это означает, что в зависимости от (временно'го) соотнесения переменная-делегат пробегает по домену значений конкретизаций связанного с ней метода.

Практика программирования на языке C# показывает, что переменная-делегат может содержать множественные значения в одно и то же время. Проиллюстрируем это утверждение следующим фрагментом программы на языке C#:

Notifier greetings;
greetings = new Notifier(SayHello);
greetings += new Notifier(SayGoodbye);
greetings("John");
// "Hello from John"
// "Good bye from John"
greetings -= new Notifier(SayHello);

greetings("John");
// "Good bye from John"

Как видно из приведенного примера, фрагмент программы на языке C# содержит описание уже известной нам переменной-делегата greetings типа-делегата Notifier. Переменной-делегату greetings в качестве значения последовательно инкрементно присваиваются конкретизации-методы SayHello и SayGoodbye. При этом отладочный вывод значения переменной greetings с конкретизацией "John" демонстрирует семейство значений "Hello from John" и "Good bye from John". После декрементного присваивания переменной-делегату greetings конкретизации-метода SayHello отладочный вывод значения переменной демонстрирует значение "Good bye from John".

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

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

Проиллюстрируем подход к управлению событиями посредством механизма делегатов следующим фрагментом программного кода на языке программирования C#:

class Model {
    public event Notifier notifyViews;
    public void Change() { 
        ...
        notifyViews("Model");
    }
}

class View1 {
    public View1(Model m) {
        m.notifyViews +=
        new Notifier(this.Update1);
    }
    void Update1(string sender){
        Console.WriteLine(
        sender + "was changed");
    }
}

class View2 {
    public View2(Model m){
        m.notifyViews +=
        new Notifier(this.Update2);
    }
    void Update2(string sender){
        Console.WriteLine(
        sender + " was changed");
    }
}

class Test {
    static void Main() {
        Model m = new Model();
        new View1(m); 
        new View2(m);
        m.Change();
    }
}

Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса Model с полем-событием notifyViews (описатель event ) и методом Change, оповещающим через делегат о смене соотнесения. Кроме того, в данном фрагменте содержатся описания класса View1 с одноименными методом для просмотра состояния делегата посредством метода Update1, содержащего вывод на стандартное устройство отладочной информации о смене приложения-"отправителя" сообщения.

Пример завершается описанием класса View2, аналогичного классу View1, а также класса Test, который инициализирует классы View1 и View2 и запускает метод Change, инициирующий смену соотнесенийсостояний ) переменных-делегатов.

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