
- •Введение в программирование на c# в .Net Что такое .Net и зачем она нужна?
- •Первая программа на c# и основные приемы работы в системе ms Visual Studio
- •Структура программы
- •Собственные пространства имен
- •Особенности языка c#
- •Полноценный логический тип данных
- •Оператор switch
- •Основные классы
- •Класс Console
- •Класс Convert
- •Строковый тип данных string (класс String)
- •Метод Split
- •Enumeration – перечислимый тип
- •Метод IndexOf()
- •Метод Format
- •Метод ToCharArray
- •Объектно-ориентированное программирование Эволюция от структур к классам Используем структуры
- •Структурный тип как параметр
- •Помещаем метод в структурный тип
- •Превращение в класс
- •Классы и объекты
- •Значимые и ссылочные переменные
- •Конструкторы класса
- •Статические элементы
- •Генерация случайных чисел
- •Массивы в языке c#
- •Многомерные массивы
- •Класс ArrayList
- •Инкапсуляция
- •Обработка ошибок
- •Свойства класса
- •Язык uml
- •Связи между объектами
- •Наследование (Inheritance)
- •Класс Object
- •Защищенные переменные
- •Вызов базового конструктора
- •Переопределение методов. Обращение к «затененным» элементам класса
- •Многоуровневое наследование
- •Полиморфизм
- •Метод ToString
- •Типичные ситуации проявления полиморфизма
- •Абстрактные классы и полиморфизм
- •Литература
- •Лекция 21. События Классы с событиями
- •Класс sender. Как объявляются события?
- •Делегаты и события
- •Как зажигаются события
- •Классы receiver. Как обрабатываются события
- •Классы с событиями, допустимые в .Net
- •Пример «Списки с событиями»
- •Две проблемы с обработчиками событий
- •Классы с большим числом событий
- •Проект «Город и его службы»
- •Вариант 1
- •Обработчик события:
- •Отметьте истинные высказывания:
- •Вариант 2
- •Вариант 3
- •Отметьте истинные высказывания:
- •Все аргументы события принадлежат:
Классы receiver. Как обрабатываются события
Объекты класса sender создают события и уведомляют о них объекты, возможно, разных классов, названных нами классами receiver или клиентами. Чтобы вся эта схема заработала, класс receiver должен:
Иметь обработчик события – процедуру, согласованную по сигнатуре с функциональным типом делегата, задающего событие.
Иметь ссылку на объект, создающий событие, чтобы получить доступ к этому событию – event объекту.
Уметь присоединить обработчик события к event объекту. Это можно реализовать по-разному, но технологично это делать непосредственно в конструкторе класса, так что когда создается объект, получающий сообщение, он изначально готов принимать и обрабатывать сообщения о событиях.
Вот пример, демонстрирующий возможное решение проблем:
public class FireMen
{ private TownWithEvents MyNativeTown;
public FireMen(TownWithEvents TWE)
{ this.MyNativeTown=TWE;
MyNativeTown.FireEvent += new FireEventHandler(FireHandler);
}
private void FireHandler(object Sender, int time, int build)
{ Console.WriteLine("Fire at day {0}, in build {1}!", time, build); }
public void GoOut()
{ MyNativeTown.FireEvent -= new FireEventHandler(FireHandler); }
}
В классе Fireman есть ссылка на объект класса TownWithEvents, создающий события. Сам объект передается в конструкторе класса. Здесь же происходит присоединение обработчика события к event объекту. Обработчик события FireHandler выводит сообщение на консоль.
Классы с событиями, допустимые в .Net
Если создавать повторно используемые компоненты с событиями, работающие не только в проекте C#, то необходимо при работе с событиями удовлетворять некоторым ограничениям. Эти требования предъявляются к делегату; они носят, скорее, синтаксический характер, не ограничивая по существу дела. Перечислим эти ограничения:
Делегат, задающий тип события, должен иметь фиксированную сигнатуру из двух аргументов:
delegate <имя_делегата> (object sender, <тип_аргументов_события> args)
Первый аргумент задает объект sender, создающий сообщение. Второй аргумент args задает остальные аргументы – входные и выходные, – передаваемые обработчику. Тип этого аргумента должен задаваться классом, производным от встроенного в .NET класса EventArgs. Если обработчику никаких дополнительных аргументов не передается, то следует просто указать класс EventArgs, передавая null в качестве фактического аргумента при включении события.
Рекомендуемое имя делегата – составное, начинающееся именем события, после которого следует слово EventHandler, например, FireEventHandler. Если никаких дополнительных аргументов обработчику не передается, то тогда можно вообще делегата не объявлять, а пользоваться стандартным делегатом с именем EventHandler.
Пример «Списки с событиями»
В этом примере строится класс ListWithChangedEvent, являющийся потомком встроенного класса ArrayList, позволяющего работать со списками. В класс добавляется событие Changed, сигнализирующее обо всех изменениях элементов списка. Строятся два класса Receiver1 и Receiver2, получающие сообщения. В примере рассматривается взаимодействие нескольких объектов – два объекта посылают сообщения, три – принимают.
Начнем с объявления делегата:
public delegate void ChangedEventHandler(object sender,
ChangedEventArgs args);
Здесь объявлен делегат ChangedEventHandler по всем правилам хорошего стиля – его имя и его форма соответствует всем требованиям. Второй аргумент, задающий аргументы события, принадлежит классу ChangedEventArgs, производному от встроенного класса EventArgs. Рассмотрим, как устроен этот производный класс:
public class ChangedEventArgs:EventArgs
{ private object item;
private bool permit;
public object Item
{ get {return(item);} set { item = value;} }
public bool Permit
{ get {return(permit);} set { permit = value;} }
}
}
У класса два закрытых свойства, доступ к которым осуществляется через процедуры-свойства get и set. Конечно, можно было бы в данной ситуации сделать их просто public – общедоступными. Свойство Item задает входной аргумент события, передаваемый обработчику события. Булево свойство Permit задает выходной аргумент события, получающий в обработчике значение true, если обработчик события дает добро на изменение элемента.
В модели, которую мы рассматриваем, предполагается, что обработчик события, получив уведомление об изменении элемента, анализирует ситуацию и может разрешить или не разрешить изменение, например, если значение элемента больше некоторого предельного значения.
Правильно ли, что обработчик события, а не сам класс, создающий событие, принимает решение о допуске изменения элемента списка? Все зависит от контекста. В прошлые времена молодые могли объявить о своей помолвке, но требовалось разрешение родителей на брак. Времена изменились, теперь на брак родительского благословения не требуется. Но в программистском мире ситуации, требующие внешнего разрешения, встречаются довольно часто.
Класс sender
Рассмотрим теперь, как устроен в нашем примере класс, создающий события. Начнем со свойств класса:
public class ListWithChangedEvent: ArrayList
{ //Свойства класса: событие и его аргументы
//Событие Changed, зажигаемое при всех изменениях элементов списка.
public event ChangedEventHandler Changed;
//Аргументы события
private ChangedEventArgs evargs = new ChangedEventArgs();
Первое свойство описывает событие Changed. Оно открыто, что позволяет присоединять к нему обработчиков событий. Второе закрытое свойство определяет аргументы события, передаваемые обработчикам.
Хороший стиль требует задания в классе процедуры On, включающей событие. Так и поступим:
//Методы класса: процедура On и переопределяемые методы.
// Процедура On, включающая событие
protected virtual void OnChanged(ChangedEventArgs args)
{ if (Changed != null) Changed(this, args); }
Процедура OnChanged полностью соответствует ранее описанному образцу, поэтому не требует дополнительных комментариев.
Наш класс, являясь наследником класса ArrayList, наследует все его методы. Переопределим методы, изменяющие элементы:
метод Add, добавляющий новый элемент в конец списка;
индексатор this, дающий доступ к элементу списка по индексу;
метод Clear, производящий чистку списка:
// Переопределяемые методы, вызывающие событие Changed
//Добавление нового элемента при получении разрешения у обработчиков события
public override int Add(object value)
{ int i=0;
evargs.Item = value;
OnChanged(evargs);
if (evargs.Permit) i = base.Add(value);
else Console.WriteLine("Добавление запрещено." + "Значение={0}", value);
return i;
}
public override void Clear()
{ evargs.Item=0;
OnChanged(evargs);
base.Clear();
}
public override object this[int index]
{ set
{ evargs.Item = value;
OnChanged(evargs);
if (evargs.Permit) base[index] = value;
else
Console.WriteLine("Замена элемента запрещена. Значение={0}", value);
}
get{return(base[index]);}
}
Обратите внимание на схему включения события, например, в процедуре Add. Вначале задаются входные аргументы события, в данном случае Item. Затем вызывается процедура включения события OnChanged. При зажигании события выполнение процедуры Add прерывается. Запускаются обработчики, присоединенные к событию. Процедура Add продолжит работу только после окончания их работы. Анализ выходной переменной Permit позволяет установить, получено ли разрешение на изменение значения; при истинности значения этой переменной вызывается родительский метод Add, осуществляющий изменение значения. Это достаточно типичная схема работы с событиями.
Классы receiver
Мы построим два класса, объекты которых способны получать и обрабатывать событие Changed. Получать они будут одно и тоже сообщение, а обрабатывать его будут по-разному. В нашей модельной задаче различие обработчиков сведется к выдаче разных сообщений. Поэтому достаточно разобраться с устройством одного класса, названного EventReceiver1. Вот его код:
class EventReceiver1
{ private ListWithChangedEvent List;
public EventReceiver1(ListWithChangedEvent list)
{ List = list;
// Присоединяет обработчик к событию.
OnConnect();
}
// Обработчик события - выдает сообщение.
//Разрешает добавление элементов, меньших 10.
private void ListChanged(object sender, ChangedEventArgs args)
{ Console.WriteLine("EventReceiver1: Сообщаю об изменениях:"
+ "Item ={0}", args.Item);
args.Permit = ((int)args.Item < 10);
}
public void OnConnect()
{ // Присоединяет обработчик к событию
List.Changed += new ChangedEventHandler(ListChanged);
}
public void OffConnect()
{ // Отсоединяет обработчик от события и удаляет список
List.Changed -= new ChangedEventHandler(ListChanged);
List = null;
}
}//class EventReceiver1
Дам краткие комментарии:
Среди закрытых свойств класса есть ссылка List на объект, создающий события.
Конструктору класса передается фактический объект, который и будет присоединен к List. В конструкторе же и происходит присоединение обработчика события к событию. Для этого, как и положено, используется созданный в классе метод OnConnect.
Класс содержит метод OffConnect, позволяющий при необходимости отключить обработчик от события.
Обработчик события, анализируя переданный ему входной аргумент события Item, разрешает или не разрешает изменение элемента, формируя значение выходного аргумента Permit. Параллельно обработчик выводит на консоль сообщение о своей работе.
Класс Reciver2 устроен аналогично. Приведу его текст уже без всяких комментариев:
class Receiver2
{ private ListWithChangedEvent List;
public Receiver2(ListWithChangedEvent list)
{ List = list;
// Присоединяет обработчик к событию.
OnConnect();
}
// Обработчик события - выдает сообщение.
//Разрешает добавление элементов, меньших 20.
private void ListChanged(object sender, ChangedEventArgs args)
{ Console.WriteLine("Receiver2: Сообщаю об изменениях:"
+ " Объект класса {0} : "
+ "Item ={1}", sender.GetType(), args.Item);
args.Permit = ((int)args.Item < 20);
}
public void OnConnect()
{ // Присоединяет обработчик к событию
List.Changed += new ChangedEventHandler(ListChanged);
//Заметьте, допустимо только присоединение (+=), но не замена (=)
//List.Changed = new ChangedEventHandler(ListChanged);
}
public void OffConnect()
{ // Отсоединяет обработчик от события и удаляет список
List.Changed -= new ChangedEventHandler(ListChanged);
List = null;
}
}//class Receiver2
Классы созданы, теперь осталось создать объекты и заставить их взаимодействовать, чтобы одни создавали события, а другие их обрабатывали. Эту часть работы будет выполнять тестирующая процедура класса Testing:
public void TestChangeList()
{ // Создаются два объекта, вырабатывающие события
ListWithChangedEvent list = new ListWithChangedEvent();
ListWithChangedEvent list1 = new ListWithChangedEvent();
// Создаются три объекта двух классов EventReceiver1 и Receiver2,
//способные обрабатывать события класса ListWithChangedEvent
EventReceiver1 Receiver1 = new EventReceiver1(list);
Receiver2 Receiver21 = new Receiver2 (list);
Receiver2 Receiver22 = new Receiver2(list1);
Random rnd = new Random();
// Работа с объектами, приводящая к появлению событий
list.Add(rnd.Next(20)); list.Add(rnd.Next(20)); list[1] =17;
int val = (int)list[0] + (int)list[1];list.Add(val);
list.Clear();
list1.Add(10); list1[0] = 25; list1.Clear();
//Отсоединение обработчика событий
Receiver1.OffConnect();
list.Add(21); list.Clear();
}
В заключение взгляните на результаты работы этой процедуры:
Рис. 21.2. События в мире объектов