Слайд 2Делегаты
Делегат — это объект, который может ссылаться на метод -
создавая делегат, мы создаем объект, который может содержать ссылку на
метод и этот метод можно вызвать посредством соответствующей ссылки. Т. о., делегат может вызывать метод, на который он ссылается.
Делегат – это так называемый “безопасный указатель на функцию”(делегаты C# = указатель на функцию в С)
Делегаты C# могут вызывать более одной функции (при совместном комбинировании двух делегатов результатом будет делегат, который вызывает их обоих).
Делегат - это класс отнаследованный от базового класса System.MulticastDelegate.
Делегат можно объявлять как в классе, так и просто в пространстве имен.
Слайд 3во время выполнения программы один и тот же делегат можно
использовать для вызова различных методов, просто заменив метод, на который
ссылается этот делегат (метод, который будет вызван делегатом, определяется не в период компиляции программы, а во время ее работы (достоинство делегата)).
delegate тип_возврата имя(список_параметров);
тип_возврата - собой тип значений, возвращаемых методами, которые этот делегат будет вызывать.
список_параметров - параметры, принимаемые методами, которые вызываются посредством делегата,
Делегат может вызывать только такие методы, у которых тип возвращаемого значения и список параметров (т.е. его сигнатура) совпадают с соответствующими элементами объявления делегата.
Делегат может вызывать либо метод экземпляра класса, связанный с объектом, или статический метод, связанный с классом.
Слайд 4Создание делегата
Объявляется сам делегат как тип и сразу же задается
прототип функций, которые в будущем могут адресоваться экземплярами этого типа.
Объявляется
ссылка типа делегата, которая будет указывать на коллекцию экземпляров делегата.
Создаются сами объекты делегата, каждый из которых адресует одну функцию, и комбинируются в коллекцию, на которую указывает объявленная ссылка.
Коллекция - собой список адресуемых делегатом функций и поддерживается операциями += и -= или статическими методами Delegate.Combine() и Delegate.Remove()
Экземпляр делегата способен вызывать эти методы либо по одному, либо сразу весь список.
Слайд 5Одноадресная работа делегатов
Во время выполнения программы один и тот же
делегат можно использовать для вызова разных методов одинаковой сигнатуры простой
заменой метода, на который ссылается делегат (главное достоинство делегата, что адресуемый им метод определяется не на этапе компиляции, а в период выполнения). Заменяя в делегате адресуемые функции мы обеспечим их полиморфный вызов.
Область видимости делегата можно регулировать точно так же, как и область видимости любого другого типа в приложении.
Слайд 6class 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 = "Применение делегатов";
new MyClass(); // Чтобы сработал конструктор
} }
Слайд 7Многоадресная работа делегатов
Многоадресатная передача — это способность создавать список вызовов
(или цепочку вызовов) методов, которые должны автоматически вызываться при вызове
делегата.
Делегат с многоадресатной передачей имеет одно ограничение: он должен возвращать тип void.
Надо создать экземпляр делегата, а затем для добавления методов в эту цепочку использовать оператор "+=".
Для удаления метода из цепочки используется оператор " - = " .
Слайд 8// Для методов, имеющих одинаковую сигнатуру и не возвращающих значение
(только с void) с помощью делегата можно организовать сразу цепочку вызовов
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 = "Многоадресные делегаты";
new MyClass(); // Чтобы сработал конструктор
} }
Слайд 9Делегаты являются экземплярами типа System.MulticastDelegate, который в свою очередь наследует абстрактный
класс System.Delegate.
Экземпляр типа MulticastDelegate может хранить в себе одну или сразу несколько
ссылок на методы. В любом случае мы не можем явно объявлять делегат с помощью типа MulticastDelegate.
Это делается с помощью ключевого слова delegate, но неявно порождается объект класса MulticastDelegate.
Структура примера через панель Class View.
Слайд 10Пустые делегаты
Делегат существует как объект до тех пор, пока его
список с адресуемыми функциями содержит хотя бы одну функцию.
Если
список делегата становится пустым, то он превращается в обычную ссылочную переменную с нулевой адресацией.
Т.к. мы можем не только пополнять список, но и удалять из него некоторые функции, то необходимо следить за тем, чтобы список делегата не оказался пустым, иначе при попытке вызова такого списка будет выброшено исключение.
Если делегат имеет пустой список, т.е. не ссылается ни на одну функцию, то представляющая его ссылка имеет нулевой адрес
Слайд 11class 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;
new MyClass(); // Исполняем
} }
Слайд 12Члены типа MulticastDelegate, унаследованные от Delegate
Слайд 13//Применение свойства Method
class MultiTest {
//
Статическое поле
static int x = 1;
// Объявляем функции с одинаковой сигнатурой
public void Handler1() {
Console.WriteLine("Исполняется Handler1"); }
public void Handler2() {
Console.WriteLine("Исполняется Handler2"); }
public void Handler3() {
Console.WriteLine("Исполняется Handler3"); }
}
// Вызывающая сторона
class MyClass {
public static string Title = "Свойство Method“;
// Объявляем делегат как член класса
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.Method.Invoke(obj, null); // Вызываем последний метод
Console.WriteLine("\nПрототип последнего метода:\n{0}",
del.Method.ToString());
Console.WriteLine("\nИмена адресуемых методов:");
Delegate[] listMethods = del.GetInvocationList();
for (int i = 0; i < listMethods.Length; i++)
Console.WriteLine("{0}", listMethods[i].Method.Name); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
Слайд 14//Свойство Target - свойство возвращает объект, связанный с адресуемым делегатом
методом
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 = "Применение свойства Target делегата";
// Объявляем делегат как член класса
delegate void MyDelegate();
public MyClass() {
// Создаем экземпляр класса с методами
MultiTest obj = new MultiTest();
// Создаем объект-делегат и заполняем ссылками на функции
// Инициализируем первой ссылкой
MyDelegate del = new MyDelegate(obj.Handler1);
// Добавляем другие ссылки
del += obj.Handler2; del += obj.Handler3;
// Вызываем цепочку методов
Console.WriteLine("Многоадресный вызов через делегат");
del();
Console.WriteLine();
// Возвращает объект, с которым связан
// последний адресуемый метод
Console.WriteLine("Тип объекта: {0}",
del.Target.GetType().ToString());
Console.WriteLine();
// Повышаем полномочия извлеченной из Target ссылки
// на объект и повторно адресуемся напрямую
Console.WriteLine("Вызов через свойство Target делегата");
((MultiTest)del.Target).Handler1();
((MultiTest)del.Target).Handler2();
((MultiTest)del.Target).Handler3();
Console.WriteLine();
// Адресуемся напрямую без делегата
Console.WriteLine("Вызов через объект напрямую");
obj.Handler1(); obj.Handler2();
obj.Handler3(); } }
Слайд 15
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
Слайд 16Методы DynamicInvoke() и GetInvocationList()
Позволяют вызывать отдельные члены, адресуемые списком объекта-делегата,
и задавать требуемые аргументы.
Если член списка не имеет аргументов,
то в качестве параметра метода используется null, иначе - массив параметров адресуемого члена.
Объявление ссылки на объект-делегат прописывает только прототип методов, которые корректно может адресовать эта ссылка.
Делегат как объект создается при заполнении списка адресуемыми функциями. Но при формировании списка в него добавляются только имена методов. Это приемлемо, если вызываются функции с пустой сигнатурой.
Слайд 17class ShowPerson {
// Объявляем функцию
с аргументами
public static void Handler(string name,
int age) {
Console.WriteLine("Сотрудник {0}, возраст {1}", name, age); } }
// Вызывающая сторона
class MyClass {
public static string Title = "Вызов методов с параметрами”;
// Объявляем делегат как член класса
delegate void MyDelegate(string name, int age);
public MyClass() {
// Создаем и заполняем объект-делегат
MyDelegate del = new MyDelegate(ShowPerson.Handler);
// Добавляем другие ссылки
int count = 3;
for (int i = 1; i < count; i++) {
del += ShowPerson.Handler; }
// Вызываем цепочку методов с одинаковым параметром
del("Иванов", 21); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
все зарегистрированные в делегате методы стороннего класса получают, при вызове их с помощью делегата, одинаковую входную информацию
Слайд 18если с помощью делегата нужно адресовать методы, имеющие разные значения
параметров используется
public object DynamicInvoke(params object[ ] args)
с помощью метода DynamicInvoke() можно
решить проблему надежности кода при адресации вызовов методов посредством делегата.
Если любой из цепочки метод при традиционном вызове может дать сбой, то в результате система выдаст исключение и прервет вызовы остальных методов списка(исключение можно обработать, но только для списка в целом, если не контролируется индивидуально каждый член списка делегата).
Эти проблемы решает метод DynamicInvoke() совместно с GetInvocationList()
Слайд 19class ShowPerson {
// Функция с
аргументами
public static void Handler(string name, int
age) {
Console.WriteLine("Сотрудник {0}, возраст {1}", name, age); }
// Проблемная функция с нормальным прототипом
public static void ProblemHandler(string name, int age) {
// Преднамеренно выбрасываем исключение
throw new Exception(); } }
// Вызывающая сторона
class MyClass {
public static string Title = "Применение DynamicInvoke()“;
// Объявляем делегат
delegate void MyDelegate(string name, int age);
public MyClass() {
// Формируем список объекта-делегата
// Добавляем в список один проблемный метод
MyDelegate del = new MyDelegate(ShowPerson.ProblemHandler);
// Добавляем еще три нормальных метода
int count = 3;
for (int i = 0; i < count; i++)
{ del += ShowPerson.Handler; }
object[] param = new object[2]; // Объявили массив для параметров
int j = 0; // Объявили и инициализировали счетчик
// Перебираем список вызовов делегата, включая и вызов проблемного метода
foreach (Delegate d in del.GetInvocationList()) {
// Индивидуально формируем параметры методов
switch (j) {
case 0: // Можно и не задавать, все равно для проблемного метода!
param[0] = "Мистер X";
param[1] = 99; break;
// Для вызовов нормального метода
case 1: param[0] = "Иванов";
param[1] = 21; break;
case 2: param[0] = "Петров";
param[1] = 22; break;
case 3: param[0] = "Сидоров";
param[1] = 23; break; }
j++; // Счетчик
// Защищено вызываем адресуемые методы индивидуально
try {
d.DynamicInvoke(param); }
catch (Exception exc) {
string str = d.Method.Name;
Console.WriteLine("Сбой метода {0}!!!", str);
str = exc.Message; // Системное сообщение
// Разбиваем длинное сообщение пополам
int pos = str.Length / 2;
pos = str.IndexOf(' ', pos); // От средины первый пробел
Console.WriteLine("\"" + // Экранируем кавычки
str.Substring(0, pos) + Environment.NewLine + str.Substring(pos + 1) + "\""); // Экранируем кавычки
Console.WriteLine(); // Отделяем сообщение
} } } }
Слайд 20
// Запуск
class Program
{
static void Main()
{
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
}
}
Слайд 21Перегруженные операторы 'operator ==' и 'operator !='
Эти операторы позволяют подтвердить
или опровергнуть абсолютную идентичность списков функций сравниваемых делегатов.
Слайд 22class Handler {
// Функции
public void Handler1() {
}
public void Handler2() { ; }
}
// Вызывающая сторона
class MyClass {
public static string Title = "Применение операторов '==' и '!='";
// Объявляем делегат
delegate void MyDelegate();
// Объявляем ссылки на делегаты как
// поля для видимости в методах класса
MyDelegate del0, del1;
public MyClass() {
// Создаем объект
Handler obj = new Handler();
// Формируем список вызовов объекта-делегата
del0 = new MyDelegate(obj.Handler1);
del0 += new MyDelegate(obj.Handler2);
// Еще один делегат с тем же списком вызовов
del1 = new MyDelegate(obj.Handler1);
del1 += obj.Handler2; // Упрощенный синтаксис
// Сравниваем делегаты с полностью совпадающими списками
Compare();
// Делегат прежним содержимым, но в другом порядке
del1 = new MyDelegate(obj.Handler2);
del1 += obj.Handler1; // Упрощенный синтаксис
// Сравниваем делегаты с одинаковым содержимым, но разным порядком
Compare();
// Изменяем содержимое одного из делегатов
del0 -= obj.Handler2;
// Опять сравниваем делегаты с разным содержимым
Compare(); }
void Compare() {
if (del0 == del1)
Console.WriteLine("Списки делегатов идентичны");
else
Console.WriteLine("Списки делегатов различны"); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
Слайд 23Методы Combine() и Remove()
Это статические методы, способные получать новый делегат
как объединение списков двух делегатов одного и того же типа,
или получать новый делегат с усеченным списком при тех же условиях.
Методы могут использоваться вместо перегруженных операций '+=' или '-=' при последовательном изменении списка одиночными функциями по синтаксису
MyDelegate del;
del = new MyDelegate(obj.Handler1);
del += new MyDelegate(obj.Handler2);
// Или del = new MyDelegate(obj.Handler1);
del = (MyDelegate)Delegate.Combine(del, new MyDelegate(obj.Handler2));
Слайд 24class Handler {
// Функции
public void Handler1() {
Console.WriteLine("Вызов Handler1()"); }
public void Handler2() {
Console.WriteLine("Вызов Handler2()"); }
}
// Вызывающая сторона
class MyClass {
public static string Title = "Применение Combine() и Remove()“;
// Объявляем делегат как член класса
delegate void MyDelegate();
public MyClass() {
// Создаем объект
Handler obj = new Handler();
// Формируем список объекта-делегата из 2 вызовов
MyDelegate del1 = new MyDelegate(obj.Handler1);
del1 += new MyDelegate(obj.Handler1);
// Еще один делегат того же типа из 3 вызовов
MyDelegate del2 = new MyDelegate(obj.Handler2);
del2 += obj.Handler2; // Упрощенный синтаксис
del2 = del2 + obj.Handler2; // То же самое
// Новый делегат из 5 вызовов
MyDelegate del3 = (MyDelegate)Delegate.Combine(del1, del2);
// Вызываем 5 функций
del3();
Console.WriteLine();
// Вновь формируем делегаты
del1 = new MyDelegate(obj.Handler1);
del1 += obj.Handler2;
// Усекаем первый список вторым
del2 = (MyDelegate)Delegate.Remove(del3, del1);
// Вызываем оставшиеся 3 функции
del2(); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass();// Исполняем
Console.ReadLine(); } }
Слайд 25События в C#
Рассылка сообщений с помощью делегата
Событие — это автоматическое
уведомление о выполнении некоторого действия.
События работают следующим образом:
Объект, которому
необходима информация о некотором событии, регистрирует обработчик для этого события.
Когда ожидаемое событие происходит, вызываются все зарегистрированные обработчики ( обработчики событий представляются делегатами).
События — это члены класса, которые объявляются с использованием ключевого слова event.
event событийный_делегат объект;
событийный_делегат - имя делегата, используемого для поддержки объявляемого события,
элемент объект — это имя создаваемого событийного объекта.
если список адресатов пуст, то самого объекта-делегата не существует и ссылка на него имеет значение null. Без проверки этого обстоятельства при попытке вызова адресатов пустой ссылкой-делегатом будет сгенерировано стандартное исключение NullReferenceException.
Слайд 26// Образец сообщения определяется делегатом
delegate void Message(string message);
// Источник сообщения
class SourceMessage {
// Общедоступное поле ссылки на объект-делегат,
// который наполнится указателями
// на функции в классах-получателях
public Message mail;
// Необязательное поле с рассылаемым сообщением
public string message;
// Разослать сообщение - функция диспетчеризации
public void DispatchMessage(string mess) {
// Сохраняем внешнее сообщение во внутреннем поле
message = mess;
// Инициируем рассылку сообщения всем,
// кто зарегистрировался в объекте-делегате
if (mail != null) // Если не пустой делегат
mail(mess); } }
// Получатель сообщения
class Addressee1 {
// Функции
public void Handler(string message) {
Console.WriteLine("Addressee1 получил:"
+ "\n\t\"{0}\"", message); } }
// Получатель сообщения
class Addressee2 {
// Функции
public void Handler(string message) {
Console.WriteLine("Addressee2 получил:"
+ "\n\t\"{0}\"", message); } }
// Вызывающая сторона
class MyClass {
static public string Title = "Рассылка сообщений делегатом";
public MyClass() {
// Создаем объекты источника и получателей сообщения
SourceMessage source = new SourceMessage();
Addressee1 obj1 = new Addressee1();
Addressee2 obj2 = new Addressee2();
// Формируем список вызовов объекта-делегата
source.mail += new Message(obj1.Handler);
source.mail += new Message(obj2.Handler);
// Рассылаем сообщение напрямую через делегат
source.mail("Первое сообщение");
Console.WriteLine();
// Рассылаем сообщение через функцию диспетчеризации
source.DispatchMessage("Второе сообщение"); }
}
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
Слайд 27Превращение делегата в событие
Разработчики C# и библиотеки .NET Framework решили объявлять ссылку на объект
делегата, когда он используется для рассылки сообщений, с ключевым словом event и
называть ее событием.
Особенность:
Событие-делегат является собственностью класса-источника и вызывать его напрямую из класса-приемника как поле сообщений нельзя.
Событие должно инициироваться функцией-членом класса, отсылающего сообщение (функция диспетчеризации), в котором оно для этих целей и объявлено.
В приемнике сообщения можно только подписаться на событие источника путем прикрепления нужных функций-обработчиков, а далее вызвать функцию диспетчеризации владельца события.
Наполнение списка события конкретными адресами функций выполняют объекты-делегаты.
Объявление в классе-источнике делегата как события означает только возможность на него подписаться функциям внешнего кода, но не гарантирует, что на него действительно кто-то подпишется в момент возбуждения этого события в классе-источнике. Поэтому функция диспетчеризации вначале проверяет, не пустой ли делегат, и только потом выполняет вызов связанных с ним методов-обработчиков.
Слайд 28 // Образец сообщения определяется делегатом
delegate void
Message(string message);
// Источник сообщения
class
SourceMessage {
// Общедоступное поле ссылки на объект-делегат,
// который теперь называется событием и наполняется
// указателями на функции в классах-получателях
public event Message mail;
// Необязательное поле с рассылаемым сообщение
public string message;
// Разослать сообщение - функция диспетчеризации
public void DispatchMessage(string mess) {
// Сохраняем внешнее сообщение во внутреннем поле
message = mess;
// Инициируем рассылку сообщения всем,
// кто зарегистрировался в объекте-делегате
if (mail != null) // Если не пустой делегат
mail(mess); } }
// Получатель сообщения
class Addressee1 {
// Функции
public void Handler(string message) {
Console.WriteLine("Addressee1 получил:"
+ "\n\t\"{0}\"", message); } }
// Получатель сообщения
class Addressee2 {
// Функции
public void Handler(string message) {
Console.WriteLine("Addressee2 получил:"
+ "\n\t\"{0}\"", message); } }
// Вызывающая сторона
class MyClass {
static public string Title = "Рассылка сообщений событием”;
public MyClass() {
// Создаем объекты источника и получателей сообщения
SourceMessage source = new SourceMessage();
Addressee1 obj1 = new Addressee1();
Addressee2 obj2 = new Addressee2();
// Формируем список обработчиков события с
//помощью объектов-делегатов
source.mail += new Message(obj1.Handler);
source.mail += new Message(obj2.Handler);
// Рассылаем сообщение только через функцию-член источника
source.DispatchMessage("Первое сообщение");
Console.WriteLine();
// Рассылаем сообщение через функцию диспетчеризации
source.DispatchMessage("Второе сообщение"); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass();// Исполняем
} }
функция диспетчеризации
DispatchMessage(), имитирующая событие
Слайд 29
Когда вместо объекта-делегата для передачи сообщений используется объект-событие, то включенные
в список события функции называют обработчиками.
Клиенты объекта с событием могут
подписаться на его обработку.
При возникновении события обработчики клиентов будут выполняться в том порядке, в котором они следуют в списке события.
Сбой любого обработчика прервет выполнение оставшихся членов списка события(как и в случае с делегатами)
Слайд 30Типичный способ создания событий
В соответствии с теорией объектно-ориентированного программирования событие
должно передаваться объектом-источником при возникновении определенных обстоятельств.
Другие классы, содержащие
в себе объект с общедоступным полем-событием, по отношению к этому событию могут выступать только как клиенты (внешний код).
Они могут только подписаться традиционным способом на событие и прослушивать его, а при получении - реагировать кодом прикрепленных к событию своих обработчиков.
Слайд 31// Образец сообщения определяется делегатом
delegate void Message(object sender,
string message);
// Класс-источник сообщения
class SourceMessage
{
// Общедоступное поле ссылки на событие
public event Message Mail;
// Метод диспетчеризации события, объявили виртуальным
// и защищенным для возможности замещения в наследниках
protected virtual void OnMail(string mess) {
// Инициируем рассылку сообщения всем, кто подписался на событие
if (Mail != null) // Если не пустой делегат
Mail(this, mess); } // Инициируем событие
// Объявляем и инициируем внутреннее поле базовым сообщением
string message = "Сообщаю, что сработал таймер!!!\n” + "Текущее время ";
// Объявляем внутреннее поле для видимости в методах
System.Timers.Timer timer;
// Конструктор класса-источника сообщения
public SourceMessage() {
// Создаем и запускаем таймер, который по истечении
// заданного времени инициирует рассылку сообщения
timer = new System.Timers.Timer();
timer.Interval = 5000d; // Сработает через 5 секунд
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Start(); } // Запускаем таймер
// Инициирование события из внешнего источника
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
DateTime curTime = DateTime.Now; // Извлекаем текущее системное время
в классе-источнике сообщения мы объявили поле-ссылку на событие как общедоступную, чтобы в любом классе-клиенте, содержащем объект с событием, эта ссылка была видна и позволяла подписать обработчики
Для инициации события мы в качестве внешней причины применили срабатывание системного таймера. В реальных условиях это может быть что угодно, но чаще всего события генерируются операционной системы, как реакция на действия пользователя или как ответ на переход программных объектов в определенные состояния.
Слайд 32 // Дополняем базовое сообщение
временем
message += curTime.ToString();
OnMail(message); // Вызываем метод диспетчеризации
timer.Stop(); // Останавливаем системный таймер
} }
// Класс-получатель сообщения
class MyClass {
static public string Title = "Передача сообщения событием (через 5 секунд!)";
public MyClass() {
// Создаем объект с событием
SourceMessage obj = new SourceMessage();
// Подписываем на событие обработчик,
// печатающий полученную информацию,
// с помощью объекта-делегата
obj.Mail += new Message(obj_Mail); }
// Обработчик. Вызывается автоматически при возникновении причины
void obj_Mail(object sender, string message) {
String str = "Получена информация от класса “ + sender.GetType().Name + ":\n“ + "\"" + message + "\"";
Console.WriteLine(str); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = MyClass.Title;
new MyClass(); } } // Исполняем
Слайд 33Модель программирования, основанная на событиях, сейчас является наиболее популярной, а
для некоторых задач - и единственно возможной.
Она наиболее приспособлена
для построения интерактивных приложений, где требуется организовать диалог с пользователем.
Когда пользователь не производит никаких действий, приложение находится в состоянии простоя (idle - простой) и требует минимальных ресурсов компьютера.
Слайд 34Создание событий с контролем адресатов
Используя библиотечные классы, содержащие события:
Подписываем в
клиенте обработчики на уже готовые события
Влияние на событие библиотечного
класса:
событие объявляется общедоступными и может наследоваться как обычный член класса( Это дает право расширить библиотечный класс, в котором можно выполнить какие-то действия: скрыть событие, объявив его заново с ключевым словом new как закрытое, или переопределить диспетчер события. )
Диспетчер в библиотечных классах обычно объявляется виртуальным и может быть переопределен в производном классе или даже совсем скрыт для следующих потомков его переобъявлением.
Для решения этих задач существует другой способ создания события в классе-источнике, который является более гибким и его называют расширенным(этот способ позволяет отслеживать в классе, содержащем событие, те обработчики, которые клиентский код будет присоединять к нему).
Создание события расширенным способом :
вместо базового поля используется закрытое поле-делегат
используются ключевые слова add и remove,
методы add и remove автоматически вызываются при добавлении обработчика в список делегата или удаления его из списка.
Слайд 35// Объявление делегата как типа
delegate void Message(object
sender, string message);
// Класс-источник сообщения
class SourceMessage {
// Это был стандартный способ
// public event Message Mail;
// Это расширенный способ
// Объявление внутреннего поля как экземпляра делегата
private Message mail;
// Создание события с контролем адресатов
public event Message Mail {
add // Контролируем добавление обработчиков в список
{
// Имя получателя сообщения
string targetName = value.Target.GetType().Name;
// Выявляем того, кого не любим!
if (targetName == "BadClass")
// Ласково уведомляем
Console.WriteLine("Типу BadClass доступ к событию запрещен!\n");
else mail += value; }
remove // Удаление контролировать не будем.
//"Его там не стояло!"
{ mail -= value; } }
// Все остальное то-же самое, кроме замены
// события Mail на закрытое поле-делегат mail
// Метод диспетчеризации события. Объявили виртуальным
// и защищенным для возможности замещения в наследниках
protected virtual void OnMail(string mess) {
// Инициируем рассылку сообщения всем,
// кто подписался на событие
// Здесь используется поле-делегат, внутри можно (и нужно!)
if (mail != null) // Если не пустой делегат
mail(this, mess); // Инициируем событие
}
// Объявляем и инициируем внутреннее поле базовым сообщением
string message = "Сообщаю, что сработал таймер!!!\n"
+ "Текущее время ";
// Объявляем внутреннее поле для видимости в методах
System.Timers.Timer timer;
// Конструктор класса-источника сообщения
public SourceMessage() {
// Создаем и запускаем таймер, который по истечении
// заданного времени инициирует рассылку сообщения
timer = new System.Timers.Timer();
timer.Interval = 5000d; // Сработает через 5 секунд
timer.Elapsed += new System.Timers.ElapsedEventHandler
(timer_Elapsed);
timer.Start(); // Запускаем таймер
}
// Инициирование события из внешнего источника
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
// Извлекаем текущее системное время
DateTime curTime = DateTime.Now;
// Дополняем базовое сообщение временем
message += curTime.ToString();
Слайд 36 OnMail(message); // Вызываем
метод диспетчеризации
timer.Stop();
// Останавливаем системный таймер
} }
// Хороший класс-получатель сообщения
class GoodClass {
public GoodClass() {
// Создаем объект с событием
SourceMessage obj = new SourceMessage();
// Подписываем на событие обработчик,
// печатающий полученную информацию
obj.Mail += new Message(obj_Mail); }
// Обработчик
void obj_Mail(object sender, string message) {
String str = this.GetType().Name
+ " получил информацию от "
+ sender.GetType().Name + "\n"
+ "следующего содержания:\n\""
+ message + "\"";
Console.WriteLine(str); } }
// Плохой класс, которому не разрешено получать сообщения
class BadClass {
public BadClass() {
// Создаем объект с событием
SourceMessage obj = new SourceMessage();
// Подписываемся на событие в неведении,
// что мы попали в черный список и нас
// не включат в список рассылки
obj.Mail += new Message(obj_Mail); }
// Обработчик
void obj_Mail(object sender, string message) {
String str = "Получена информация от класса "
+ sender.GetType().Name + ":\n"
+ "\"" + message + "\"";
Console.WriteLine(str); } }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = "Расширенное создание события (через 5 секунд!)";
new GoodClass(); // Исполняем хороший класс
new BadClass(); // Исполняем плохой класс
} }
событие класса GoodClass было инициировано первым, но отработало позднее, т.к. ждало срабатывания системного таймера
Слайд 37Создание событий со списком делегатов
В библиотеке .NET Framework для структурированной поддержки событий
создан класс EventHandlerList, который может хранить в себе элементы, выполняющие
функцию делегатов с присоединенными к ним обработчиками(эти элементы маркируются ключами типа Object).
Каждому ключу соответствует один или несколько одноадресных делегатов, способных хранить вызовы обработчиков одного и того же прототипа.
За событием закрепляется определенный ключ и при манипуляции с событием во внешнем коде делегаты, маркированные этим ключом, добавляются в список.
Чтобы активизировать событие, достаточно обратиться к списку с закрепленным за событием ключом, все одноадресные делегаты будут извлечены из списка и присоединенные обработчики будут выполнены.
Поскольку элементы списка реализуют вызов обработчиков одноадресным способом, то использование списка позволяет прикреплять обработчики не только с пустым возвращаемым значением, но и с возвращаемым значением любого типа(хотя в большинстве случаев это несущественное преимущество, поскольку результат обработки можно вернуть и через аргументы обработчика).
Класс EventHandlerList находится в пространстве имен System. ComponentModel.
Слайд 38// демонстрируется создание событий на основе списка одноадресных делегатов.
// Класс-источник сообщения
class SourceMessage {
// Создание списка делегатов вместо базовых полей
System.ComponentModel.EventHandlerList eventList
= new System.ComponentModel.EventHandlerList();
// Объявление типов делегатов
public delegate void Message1();
public delegate int Message2(string message);
// Непустое возвращаемое значение
public delegate void Message3(object sender, string message);
// Создание ключей для делегатов
Object key1 = new Object();
Object key2 = new Object();
Object key3 = new Object();
// Создание события на базе списка
public event Message1 Mail1 {
add {
eventList.AddHandler(key1, value); // Дополняем список делегатов
}
remove {
eventList.RemoveHandler(key1, value); // Удаляем из списка
} }
// Создание события на базе списка
public event Message2 Mail2 {
add {
eventList.AddHandler(key2, value); // Расширяем список делегатов
}
remove {
eventList.RemoveHandler(key2, value);// Удаляем из списка
} }
// Создание события на базе списка
public event Message3 Mail3 {
add {
eventList.AddHandler(key3, value); // Расширяем список делегатов
}
remove {
eventList.RemoveHandler(key3, value); // Удаляем из списка
} }
// Симуляция срабатывания события Mail1
public void DispatchMail1() {
// Извекаем из списка все делегаты для Mail1, помеченные ключом
Message1 mail1 = (Message1)eventList[key1];
if (mail1 != null)
mail1(); }
// Симуляция срабатывания события Mail2
public void DispatchMail2() {
// Извекаем из списка все делегаты для Mail2, помеченные ключом
Message2 mail2 = (Message2)eventList[key2];
if (mail2 != null)
mail2("\"mail2 из SourceMessage\""); }
Слайд 39 // Симуляция срабатывания события Mail3
public void DispatchMail3() {
// Извекаем из списка все делегаты для Mail3, помеченные ключом
Message3 mail3 = (Message3)eventList[key3];
if (mail3 != null)
mail3(this, "\"mail3 из SourceMessage\""); } }
// Получатель сообщения
class MyClass {
// Конструктор
public MyClass() {
// Создаем объект с событиями
SourceMessage obj = new SourceMessage();
// Подписываемся на обработчики
obj.Mail1 += new SourceMessage.Message1(obj_Mail1);
obj.Mail1 += new SourceMessage.Message1(obj_Mail1);
obj.Mail2 += new SourceMessage.Message2(obj_Mail2);
obj.Mail3 += new SourceMessage.Message3(obj_Mail3);
// Запускаем события
obj.DispatchMail1();
obj.DispatchMail2();
obj.DispatchMail3(); }
// Обработчики
void obj_Mail1() {
Console.WriteLine("Обработчик события Mail1."); }
int obj_Mail2(string message) {
Console.WriteLine("\nОбработчик события Mail2.\n" +
"Сообщение: {0}\n", message);
return 1; }
void obj_Mail3(object sender, string message) {
Console.WriteLine("Обработчик события Mail3.\n" +
"Сообщение: {0}", message);
} }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = "Применение списка делегатов";
new MyClass(); // Исполняем
}
}
Слайд 40Стандартный делегат EventHandler и стандартный аргумент EventArgs
Для большинства практических случаев
обработчикам событий достаточно двух параметров, первый из которых будет адресовать
объект, возбудивший событие, а второй - некоторую дополнительную информацию.
Для этих целей существует библиотечный делегат EventHandler.
Второй аргумент делегата всегда можно расширить, если мы хотим передать с событием свою специфическую информацию.
Слайд 41// Расширение библиотечного класса аргументов
class MyEventArgs : EventArgs
{
private string message; // Поле
для хранения сообщения
// Сервис доступа к полю
public String Message {
get { return message; }
set { message = value; } } }
// Класс-источник сообщения
class SourceEvent {
// Тип стандартного делегата объявлен в mscorlib.System так
// public delegate void EventHandler(object sender, System.EventArgs e)
// Создание события на базе стандартного делегата
public event EventHandler Event;
// Метод диспетчеризации события
protected virtual void OnEvent(EventArgs args) {
if (Event != null) Event(this, args); // Вызываем обработчики
}
// Симулятор срабатывания события Event по внешней причине
public void SimulateEvent() {
// Создаем толстый объект и формируем передаваемую информацию
MyEventArgs args = new MyEventArgs();
args.Message = "'Это сообщение поступило с событием'";
// Вызываем функцию диспетчеризации события.
// Функция ожидает тонкий объект, а ей передается толстый объект - это нормально
OnEvent(args); } }
Слайд 42class MyClass {// Получатель сообщения
//
Конструктор
public MyClass()
{
// Создаем объект, имеющий событие
SourceEvent obj = new SourceEvent();
// Подписываемся на обработчики события
obj.Event += new EventHandler(Handler1);
obj.Event += new EventHandler(Handler2);
// Вызываем симулятор возникновения события
obj.SimulateEvent(); }
void Handler1(object sender, EventArgs e) {
// Хотим извлечь информацию из толстого объекта,
// значит нужно повысить полномочия тонкой ссылки
MyEventArgs args = (MyEventArgs)e;
String message = args.Message;
Console.WriteLine("(Handler1) Получена информация:\n" + message);
Console.WriteLine(); }
void Handler2(object sender, EventArgs e) {
Console.WriteLine("(Handler2) Событие из объекта {0}\n" + "Передан объект-аргумент {1}”, sender.GetType().Name, e.GetType().Name);
} }
// Запуск
class Program {
static void Main() {
// Настройка консоли
Console.Title = "Применение делегата EventHandler";
new MyClass(); // Исполняем
} }