Лекция 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, инициирующий смену соотнесений (и состояний ) переменных-делегатов.
Заметим, что события используются вместо обычных переменных-делегатов для увеличения уровня абстракции программного компонента, поскольку событие может активировать только тот класс, в котором оно описано.