Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
Класс Receiver2 устроен аналогично. Приведу его текст уже без всяких комментариев:
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(listl);
Random rnd = new Random;
//Работа с объектами, приводящая к появлению событий
list.Add(rnd.Next(20)); list.Add(rnd.Next(2 0));
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. События в мире объектов
Две проблемы с обработчиками событий
Объекты, создающие события, ничего не знают об объектах, обрабатывающих эти события. Объекты, обрабатывающие события, ничего не знают друг о друге, независимо выполняя свою работу. В такой модели могут возникать определенные проблемы. Рассмотрим некоторые из них.
Игнорирование коллег
Задумывались ли вы, какую роль играет ключевое слово event, появляющееся при объявлении события? Событие, объявленное в классе, представляет экземпляр делегата. В предыдущей лекции, когда речь шла о делегатах, их экземпляры объявлялись без всяких дополнительных ключевых слов.
Слово "event" играет важную роль, позволяя решить проблему, названную нами "игнорированием коллег". В чем ее суть? В том, что некоторые из классов Receiver могут вести себя некорректно по отношению к своим коллегам, занимающимся обработкой того же события. При присоединении обработчика события в классе Receiver можно попытаться вместо присоединения обработчика выполнить операцию присваивания, игнорируя, тем самым, уже присоединенный список обработчиков. Взгляните еще раз на процедуру OnConnect класса Receiver2;
там демонстрируется такая попытка в закомментированном операторе. Аналогично, в процедуре OffConnect вместо отсоединения (операции —) можно попытаться присвоить событию значение null, отсоединяя тем самым всех других обработчиков.С этим как-то нужно бороться. Ключевое слово "event" дает указание компилятору создать для события закрытое поле, доступ к которому можно получить только через два автоматически создаваемых для события метода: Add, выполняющий операцию присоединения и Remove, выполняющий обратную операцию отсоединения "-=". Никаких других операций над событиями выполнять нельзя. Тем самым, к счастью, решается проблема игнорирования коллег. Ошибки некорректного поведения класса Receiver ловятся еще на этапе трансляции.
Переопределение значений аргументов события
Обработчику события, как правило, передаются входные и выходные аргументы, характеризующие событие. Они необходимы, чтобы обработчик мог нужным образом обработать событие. Но работа с аргументами требует аккуратного с ними обращения. Могут возникать проблемы, связанные с тем, что обработчик может переопределить значения аргументов в процессе своей работы.
Приведенный выше пример "Работа со списками" демонстрирует не самый лучший способ определения аргументов, провоцирующий классы Receiver на некорректное обращение с аргументами. Напомню, в классе ChangedEventArgs, определяющем аргументы события, оба свойства Item и Permit являются закрытыми. Но определены процедуры — свойства Item и Permit, реализующие полный доступ к свойствам, поскольку определены обе процедуры get и set. Это несколько облегчило задачу, поскольку позволило изменять значение входного аргумента item перед зажиганием события для передачи его обработчику. Но входной аргумент оказался не защищенным, и обработчик события может не только использовать это значение для анализа, но и изменить его в качестве побочного эффекта своей работы. В этом случае другой обработчик будет работать уже с некорректным значением. Что еще хуже — это измененное значение может использовать и класс, в процессе своей дальнейшей работы. Поэтому входные аргументы события должны быть закрытыми для обработчиков событий. Это нетрудно сделать, и я приведу необходимые уточнения.
• В классе ChangedEventArgs следует изменить процедуру-свойство Item, удалив процедуру Set, разрешающую изменение свойства. В качестве компенсации в класс следует добавить конструктор с аргументом, что позволит в классе, создающем событие, создавать объект класса ChangedEventArgs с нужным значением свойства item. Приведу соответствующий код:
public object Item
{
get {return(item);}
//set { item = value;}
}
public ChangedEventArgs(object item)
{
this.item = item;
}
• В методы класса ListWithChangedEvent, зажигающие события, нужно ввести изменения. Теперь перед каждым вызовом нужно создавать новый объект, задающий аргументы. Вот измененный код:
public override int Add(object value)
{
int i=0;
ChangedEventArgs evargs = new ChangedEventArgs(value);
//evargs.Item = value;