Содержание
- 2. Делегаты Делегат – это так называемый “безопасный указатель на функцию”, делегаты C# могут вызывать более одной
- 3. Создание делегата объявляется сам делегат как тип и сразу же задается прототип функций, которые в будущем
- 4. Одноадресная работа делегатов Во время выполнения программы один и тот же делегат можно использовать для вызова
- 5. class DelegateTest { // Объявляем функции с сигнатурой делегатов public void Show1() { Console.WriteLine("Вызов: void Show1()");
- 6. Делегат может адресовать как методы объекта (экземпляра класса), так и статические методы. Надо, чтобы заголовки объявления
- 7. Многоадресная работа делегатов
- 8. // Для методов, имеющих одинаковую сигнатуру и не возвращающих значение (только с void) с помощью делегата
- 9. Делегаты являются экземплярами типа System.MulticastDelegate, который в свою очередь наследует абстрактный класс System.Delegate. Экземпляр типа MulticastDelegate
- 10. Пустые делегаты Делегат существует как объект до тех пор, пока его список с адресуемыми функциями содержит
- 11. class MultiTest { // Объявляем функции одного прототипа public void Handler1() { Console.WriteLine("Исполняется Handler1"); } public
- 12. если делегат имеет пустой список, т.е. не ссылается ни на одну функцию, то представляющая его ссылка
- 13. Некоторые члены типа MulticastDelegate Тип MulticastDelegate сам имеет несколько своих собственных членов, но большую часть он
- 14. Некоторые члены типа MulticastDelegate, унаследованные от Delegate
- 15. //Применение свойства Method class MultiTest { // Статическое поле static int x = 1; // Объявляем
- 16. //Свойство Target - свойство возвращает объект, связанный с адресуемым делегатом методом class MultiTest { // Объявляем
- 17. // Запуск class Program { static void Main() { // Настройка консоли Console.Title = MyClass.Title; new
- 18. Методы DynamicInvoke() и GetInvocationList() позволяет вызывать отдельные члены, адресуемые списком объекта-делегата, и задавать требуемые аргументы. Если
- 19. class ShowPerson { // Объявляем функцию с аргументами public static void Handler(string name, int age) {
- 20. если с помощью делегата нужно адресовать методы, имеющие разные значения параметров используется public object DynamicInvoke(params object[
- 21. class ShowPerson { // Функция с аргументами public static void Handler(string name, int age) { Console.WriteLine("Сотрудник
- 22. // Запуск class Program { static void Main() { // Настройка консоли Console.Title = MyClass.Title; new
- 23. Перегруженные операторы 'operator ==' и 'operator !=' Эти операторы позволяют подтвердить или опровергнуть абсолютную идентичность списков
- 24. class Handler { // Функции public void Handler1() { } public void Handler2() { ; }
- 25. Методы Combine() и Remove() Это статические методы, способные получать новый делегат как объединение списков двух делегатов
- 26. class Handler { // Функции public void Handler1() { Console.WriteLine("Вызов Handler1()"); } public void Handler2() {
- 27. События в C# Рассылка сообщений с помощью делегата Чтобы любые объекты могли обмениваться информацией, они должны
- 28. // Образец сообщения определяется делегатом delegate void Message(string message); // Источник сообщения class SourceMessage { //
- 29. В классе-источнике сообщения SourceMessage мы объявили два общедоступных поля: ссылку mail на объект-делегат и переменную message
- 30. Превращение делегата в событие Разработчики C# и библиотеки .NET Framework решили объявлять ссылку на объект делегата,
- 31. // Образец сообщения определяется делегатом delegate void Message(string message); // Источник сообщения class SourceMessage { //
- 32. Когда вместо объекта-делегата для передачи сообщений используется объект-событие, то включенные в список события функции называют обработчиками.
- 33. Типичный способ создания событий В соответствии с теорией объектно-ориентированного программирования событие должно передаваться объектом-источником при возникновении
- 34. // Образец сообщения определяется делегатом delegate void Message(object sender, string message); // Класс-источник сообщения class SourceMessage
- 35. // Дополняем базовое сообщение временем message += curTime.ToString(); OnMail(message); // Вызываем метод диспетчеризации timer.Stop(); // Останавливаем
- 36. Модель программирования, основанная на событиях, сейчас является наиболее популярной, а для некоторых задач - и единственно
- 37. Создание событий с контролем адресатов Используя библиотечные классы, содержащие события: Подписываем в клиенте обработчики на уже
- 38. // Объявление делегата как типа delegate void Message(object sender, string message); // Класс-источник сообщения class SourceMessage
- 39. OnMail(message); // Вызываем метод диспетчеризации timer.Stop(); // Останавливаем системный таймер } } // Хороший класс-получатель сообщения
- 40. Создание событий со списком делегатов В библиотеке .NET Framework для структурированной поддержки событий создан класс EventHandlerList,
- 41. // демонстрируется создание событий на основе списка одноадресных делегатов. // Класс-источник сообщения class SourceMessage { //
- 42. // Симуляция срабатывания события Mail3 public void DispatchMail3() { // Извекаем из списка все делегаты для
- 43. Стандартный делегат EventHandler и стандартный аргумент EventArgs Для большинства практических случаев обработчикам событий достаточно двух параметров,
- 44. // Расширение библиотечного класса аргументов class MyEventArgs : EventArgs { private string message; // Поле для
- 46. Скачать презентацию
Делегаты
Делегат – это так называемый “безопасный указатель на функцию”,
делегаты C#
Делегаты
Делегат – это так называемый “безопасный указатель на функцию”,
делегаты C#
“безопасность” – в С++ указатель на функцию – это фактически просто адрес, делегат позволяет проверять количество передаваемых параметров, возвращаемое значение и т.д.
Многие конструкции C# - это классы, так и делегат - это класс отнаследованный от базового класса System.MulticastDelegate.
Делегат можно объявлять как в классе, так и просто в пространстве имен.
Создание делегата
объявляется сам делегат как тип и сразу же задается прототип
Создание делегата
объявляется сам делегат как тип и сразу же задается прототип
объявляется ссылка типа делегата, которая будет указывать на коллекцию экземпляров делегата.
создаются сами объекты делегата, каждый из которых адресует одну функцию, и комбинируются в коллекцию, на которую указывает объявленная ссылка.
Коллекция - собой список адресуемых делегатом функций и поддерживается операциями += и -= или статическими методами Delegate.Combine() и Delegate.Remove()
Экземпляр делегата способен вызывать эти методы либо по одному, либо сразу весь список.
Одноадресная работа делегатов
Во время выполнения программы один и тот же делегат
Одноадресная работа делегатов
Во время выполнения программы один и тот же делегат
Область видимости делегата можно регулировать точно так же, как и область видимости любого другого типа в приложении.
class DelegateTest {
// Объявляем функции с сигнатурой делегатов
public void
class DelegateTest {
// Объявляем функции с сигнатурой делегатов
public void
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(); // Чтобы сработал конструктор
} }
Делегат может адресовать как методы объекта (экземпляра класса), так и статические
Делегат может адресовать как методы объекта (экземпляра класса), так и статические
Надо, чтобы заголовки объявления делегата и объявления метода совпадали как по типу возвращаемого значения, так и по сигнатуре.
Многоадресная работа делегатов
Многоадресная работа делегатов
// Для методов, имеющих одинаковую сигнатуру и не возвращающих значение (только
// Для методов, имеющих одинаковую сигнатуру и не возвращающих значение (только
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(); // Чтобы сработал конструктор
} }
Делегаты являются экземплярами типа System.MulticastDelegate, который в свою очередь наследует абстрактный класс System.Delegate.
Делегаты являются экземплярами типа System.MulticastDelegate, который в свою очередь наследует абстрактный класс System.Delegate.
Экземпляр типа MulticastDelegate может хранить в себе одну или сразу несколько ссылок на методы. В любом случае мы не можем явно объявлять делегат с помощью типа MulticastDelegate.
Это делается с помощью ключевого слова delegate, но неявно порождается объект класса MulticastDelegate.
структура примера через панель Class View.
Пустые делегаты
Делегат существует как объект до тех пор, пока его список
Пустые делегаты
Делегат существует как объект до тех пор, пока его список
Как только список делегата становится пустым, то он превращается в обычную ссылочную переменную с нулевой адресацией.
Т.к. мы можем не только пополнять список, но и удалять из него некоторые функции, то необходимо следить за тем, чтобы список делегата не оказался пустым, иначе при попытке вызова такого списка будет выброшено исключение.
class MultiTest {
// Объявляем функции одного прототипа
public void Handler1()
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 = "Пустые делегаты“;
// Объявляем делегат как член класса
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(); // Исполняем
} }
если делегат имеет пустой список, т.е. не ссылается ни на одну
если делегат имеет пустой список, т.е. не ссылается ни на одну
Некоторые члены типа MulticastDelegate
Тип MulticastDelegate сам имеет несколько своих собственных членов, но большую
Некоторые члены типа MulticastDelegate
Тип MulticastDelegate сам имеет несколько своих собственных членов, но большую
Члены типа MulticastDelegate содержат все необходимые сервисы по управлению делегатами. Некоторые из этих членов экземплярные, а некоторые - статические.
Некоторые члены типа MulticastDelegate, унаследованные от Delegate
Некоторые члены типа MulticastDelegate, унаследованные от Delegate
//Применение свойства Method
class MultiTest {
// Статическое поле
static int x
//Применение свойства Method
class MultiTest {
// Статическое поле
static int x
// Объявляем функции с одинаковой сигнатурой
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(); // Исполняем
} }
//Свойство Target - свойство возвращает объект, связанный с адресуемым делегатом методом
class
//Свойство Target - свойство возвращает объект, связанный с адресуемым делегатом методом
class
// Объявляем функции с одинаковой сигнатурой
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(); } }
// Запуск
class Program {
static void Main() {
// Настройка
// Запуск
class Program {
static void Main() {
// Настройка
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
} }
Методы DynamicInvoke() и GetInvocationList()
позволяет вызывать отдельные члены, адресуемые списком объекта-делегата, и
Методы DynamicInvoke() и GetInvocationList()
позволяет вызывать отдельные члены, адресуемые списком объекта-делегата, и
Если член списка не имеет аргументов, то в качестве параметра метода используется null, иначе - массив параметров адресуемого члена.
объявление ссылки на объект-делегат прописывает только прототип методов, которые корректно может адресовать эта ссылка.
делегат как объект создается при заполнении списка адресуемыми функциями. Но при формировании списка в него добавляются только имена методов. Это приемлемо, если вызываются функции с пустой сигнатурой.
class ShowPerson {
// Объявляем функцию с аргументами
public static void
class ShowPerson {
// Объявляем функцию с аргументами
public static void
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(); // Исполняем
} }
все зарегистрированные в делегате методы стороннего класса получают, при вызове их с помощью делегата, одинаковую входную информацию
если с помощью делегата нужно адресовать методы, имеющие разные значения параметров
если с помощью делегата нужно адресовать методы, имеющие разные значения параметров
public object DynamicInvoke(params object[ ] args)
с помощью метода DynamicInvoke() можно решить проблему надежности кода при адресации вызовов методов посредством делегата.
Если любой из цепочки метод при традиционном вызове может дать сбой, то в результате система выдаст исключение и прервет вызовы остальных методов списка. Исключение мы можем обработать, но только для списка в целом, если не будем контролировать индивидуально каждый член списка делегата.
Эти проблемы решает метод DynamicInvoke() совместно с GetInvocationList()
class ShowPerson {
// Функция с аргументами
public static void Handler(string
class ShowPerson {
// Функция с аргументами
public static void Handler(string
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(); // Отделяем сообщение
} } } }
// Запуск
class Program
{
static void Main()
{
//
// Запуск
class Program
{
static void Main()
{
//
Console.Title = MyClass.Title;
new MyClass(); // Исполняем
}
}
Перегруженные операторы 'operator ==' и 'operator !='
Эти операторы позволяют подтвердить или
Перегруженные операторы 'operator ==' и 'operator !='
Эти операторы позволяют подтвердить или
class Handler {
// Функции
public void Handler1() { }
public
class Handler {
// Функции
public void Handler1() { }
public
}
// Вызывающая сторона
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(); // Исполняем
} }
Методы Combine() и Remove()
Это статические методы, способные получать новый делегат как
Методы 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));
class Handler {
// Функции
public void Handler1() {
Console.WriteLine("Вызов Handler1()");
class 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(); } }
События в C#
Рассылка сообщений с помощью делегата
Чтобы любые объекты могли обмениваться
События в C#
Рассылка сообщений с помощью делегата
Чтобы любые объекты могли обмениваться
список вызываемых функций,
список обработчиков,
список адресатов,
список получателей и т.д.
Напомним, что если список адресатов пуст, то самого объекта-делегата не существует и ссылка на него имеет значение null. Без проверки этого обстоятельства при попытке вызова адресатов пустой ссылкой-делегатом будет сгенерировано стандартное исключениеNullReferenceException.
// Образец сообщения определяется делегатом
delegate void Message(string message);
// Источник
// Образец сообщения определяется делегатом
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(); // Исполняем
} }
В классе-источнике сообщения SourceMessage мы объявили два общедоступных поля: ссылку mail на объект-делегат и
В классе-источнике сообщения SourceMessage мы объявили два общедоступных поля: ссылку mail на объект-делегат и
Превращение делегата в событие
Разработчики C# и библиотеки .NET Framework решили объявлять ссылку на объект делегата,
Превращение делегата в событие
Разработчики C# и библиотеки .NET Framework решили объявлять ссылку на объект делегата,
Особенность:
событие-делегат является собственностью класса-источника и вызывать его напрямую из класса-приемника как поле сообщений нельзя.
Событие должно инициироваться функцией-членом класса, отсылающего сообщение (функция диспетчеризации), в котором оно для этих целей и объявлено.
В приемнике сообщения можно только подписаться на событие источника путем прикрепления нужных функций-обработчиков, а далее вызвать функцию диспетчеризации владельца события.
Наполнение списка события конкретными адресами функций выполняют объекты-делегаты.
Объявление в классе-источнике делегата как события означает только возможность на него подписаться функциям внешнего кода, но не гарантирует, что на него действительно кто-то подпишется в момент возбуждения этого события в классе-источнике. Поэтому функция диспетчеризации вначале проверяет, не пустой ли делегат, и только потом выполняет вызов связанных с ним методов-обработчиков.
// Образец сообщения определяется делегатом
delegate void Message(string message);
//
// Образец сообщения определяется делегатом
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(), имитирующее событие
Когда вместо объекта-делегата для передачи сообщений используется объект-событие, то включенные в
Когда вместо объекта-делегата для передачи сообщений используется объект-событие, то включенные в
Клиенты объекта с событием могут подписаться на его обработку.
При возникновении события обработчики клиентов будут выполняться в том порядке, в котором они следуют в списке события.
Сбой любого обработчика прервет выполнение оставшихся членов списка события(как и в случае с делегатами)
Типичный способ создания событий
В соответствии с теорией объектно-ориентированного программирования событие должно
Типичный способ создания событий
В соответствии с теорией объектно-ориентированного программирования событие должно
Другие классы, содержащие в себе объект с общедоступным полем-событием, по отношению к этому событию могут выступать только как клиенты (внешний код). Они могут только подписаться традиционным способом на событие и прослушивать его, а при получении - реагировать кодом прикрепленных к событию своих обработчиков.
// Образец сообщения определяется делегатом
delegate void Message(object sender, string message);
// Образец сообщения определяется делегатом
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;
в классе-источнике сообщения мы объявили поле-ссылку на событие как общедоступную, чтобы в любом классе-клиенте, содержащем объект с событием, эта ссылка была видна и позволяла подписать обработчики
Для инициации события мы в качестве внешней причины применили срабатывание системного таймера. В реальных условиях это может быть что угодно, но чаще всего события генерируются операционной системы, как реакция на действия пользователя или как ответ на переход программных объектов в определенные состояния.
// Дополняем базовое сообщение временем
message += curTime.ToString();
OnMail(message); //
// Дополняем базовое сообщение временем
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(); // Исполняем
}
}
Модель программирования, основанная на событиях, сейчас является наиболее популярной, а для
Модель программирования, основанная на событиях, сейчас является наиболее популярной, а для
Она наиболее приспособлена для построения интерактивных приложений, где требуется организовать диалог с пользователем.
Когда пользователь не производит никаких действий, приложение находится в состоянии простоя (idle - простой) и требует минимальных ресурсов компьютера.
Создание событий с контролем адресатов
Используя библиотечные классы, содержащие события:
Подписываем в клиенте
Создание событий с контролем адресатов
Используя библиотечные классы, содержащие события:
Подписываем в клиенте
Влияние на событие библиотечного класса:
событие объявляется общедоступными и может наследоваться как обычный член класса( Это дает право расширить библиотечный класс, в котором можно выполнить какие-то действия: скрыть событие, объявив его заново с ключевым словом new как закрытое, или переопределить диспетчер события. )
Диспетчер в библиотечных классах обычно объявляется виртуальным и может быть переопределен в производном классе или даже совсем скрыт для следующих потомков его переобъявлением.
Для решения подобных задач существует другой способ создания события в классе-источнике, который является более гибким и его называют расширенным. Этот способ позволяет отслеживать в классе, содержащем событие, те обработчики, которые клиентский код будет присоединять к нему.
Создание события расширенным способом :
вместо базового поля используется закрытое поле-делегат
используются ключевые слова add и remove,
методы add и remove автоматически вызываются при добавлении обработчика в список делегата или удаления его из списка.
// Объявление делегата как типа
delegate void Message(object sender, string
// Объявление делегата как типа
delegate void Message(object sender, string
// Класс-источник сообщения
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();
OnMail(message); // Вызываем метод диспетчеризации
timer.Stop(); // Останавливаем системный таймер
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 и было инициировано первым, но отработало позднее, т.к. ждало срабатывания системного таймера
Создание событий со списком делегатов
В библиотеке .NET Framework для структурированной поддержки событий создан
Создание событий со списком делегатов
В библиотеке .NET Framework для структурированной поддержки событий создан
За событием закрепляется определенный ключ и при манипуляции с событием во внешнем коде делегаты, маркированные этим ключом, добавляются в список. Чтобы активизировать событие, достаточно обратиться к списку с закрепленным за событием ключом, все одноадресные делегаты будут извлечены из списка и присоединенные обработчики будут выполнены.
Поскольку элементы списка реализуют вызов обработчиков одноадресным способом, то использование списка позволяет прикреплять обработчики не только с пустым возвращаемым значением, но и с возвращаемым значением любого типа. Хотя в большинстве случаев это несущественное преимущество, поскольку результат обработки можно вернуть и через аргументы обработчика. Класс EventHandlerList находится в пространстве имен System. ComponentModel.
// демонстрируется создание событий на основе списка одноадресных делегатов.
// Класс-источник
// демонстрируется создание событий на основе списка одноадресных делегатов.
// Класс-источник
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\""); }
// Симуляция срабатывания события Mail3
public void DispatchMail3() {
//
// Симуляция срабатывания события Mail3
public void DispatchMail3() {
//
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(); // Исполняем
}
}
Стандартный делегат EventHandler и стандартный аргумент EventArgs
Для большинства практических случаев обработчикам
Стандартный делегат EventHandler и стандартный аргумент EventArgs
Для большинства практических случаев обработчикам
Для этих целей существует библиотечный делегат EventHandler.
Второй аргумент делегата всегда можно расширить, если мы хотим передать с событием свою специфическую информацию.
// Расширение библиотечного класса аргументов
class MyEventArgs : EventArgs {
private
// Расширение библиотечного класса аргументов
class MyEventArgs : EventArgs {
private
// Сервис доступа к полю
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); } }