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