Интернет-журнал "Домашняя лаборатория", 2007 №9
Шрифт:
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
• В классе 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;
OnChanged(evargs);
if (evargs.Permit) i = base.Add(value);
else
Console.WriteLine("Добавление элемента запрещено." +
"Значение = {0}", value);
return i;
}
public override void Clear
{
ChangedEventArgs evargs = new ChangedEventArgs(0);
//evargs.Item=0;
OnChanged(evargs);
base.Clear;
}
public override object this[int index]
{
set
{
ChangedEventArgs evargs = new ChangedEventArgs(value);
//evargs.Item = value;
OnChanged(evargs);
if (evargs.Permit)
base[index] = value;
else
Console.WriteLine("Замена элемента запрещена." +
" Значение = {0}", value);
}
get {return(base[index]);}
}
Таким образом, обработчикам можно запретить изменение входных аргументов события. Но есть еще выходные аргументы события, значения которых определяются в обработчике; в нашем примере это аргумент Permit.
И здесь возникает коллизия интересов — каждый обработчик по своему может формировать значения выходных аргументов, не обращая внимания на результаты работы предыдущих обработчиков. Преимуществом в таких ситуациях обладает последний работающий обработчик события.
Эта проблема остается открытой, в языке C# здесь "дыра" — нет специальных средств, позволяющих избежать или, по крайней мере, предупредить о возникновении подобной ситуации. Вся ответственность лежит на программисте, который может выбрать некоторую стратегию решения проблемы, отдавая, например, предпочтение решению одного из обработчиков или вырабатывая итоговое решение, учитывающее все частные решения.