Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Другие элементы меню 21 страница




С этим как-то нужно бороться. Ключевое слово «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;

OnChanged(evargs);

if (evargs.Permit)

i = base. Add(value);

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]

{

{

ChangedEventArgs evargs = new ChangedEventArgs(value);

//evargs.Item = value;

OnChanged(evargs);

if (evargs.Permit)

base [index] = value;

Console.WriteLine("Замена элемента запрещена." +

" Значение = {0}", value);

}

get { return (base [index]);}

}

Таким образом, обработчикам событий можно запретить изменение входных аргументов события. Но есть еще выходные аргументы события, значения которых определяются в обработчике события; в нашем примере это аргумент Permit. И здесь возникает коллизия интересов, – каждый обработчик по-своему может формировать значения выходных аргументов, не обращая внимания на результаты работы предыдущих обработчиков. Преимуществом в таких ситуациях обладает последний работающий обработчик события.

Эта проблема остается открытой, в языке C# здесь «дыра» – нет специальных средств, позволяющих избежать или, по крайней мере, предупредить о возникновении подобной ситуации. Вся ответственность лежит на программисте, который может выбрать некоторую стратегию решения проблемы, отдавая, например, предпочтение решению одного из обработчиков, или вырабатывая итоговое решение, учитывающее все частные решения.

Итак, если событие имеет аргументы, то все входные аргументы должны быть закрыты для обработчиков события. Если обработчиков несколько, то лучше не использовать выходных аргументов, или аккуратно запрограммировать логику обработчиков, учитывающую решения, полученные коллегами – ранее отработавшими обработчиками события.

Классы с большим числом событий

Как было сказано, каждое событие класса представляется полем этого класса. Когда у класса много объявленных событий, а реально возникает лишь малая часть из них, то предпочтительнее динамический подход, когда память отводится только фактически возникшим событиям. Это несколько замедляет время выполнения, но экономит память. Решение зависит от того, что в данном контексте важнее – память или время. Для реализации динамического подхода в языке предусмотрена возможность задания пользовательских методов Add и Remove в момент объявления события. Это и есть другая форма объявления события, упоминавшаяся ранее. Вот ее примерный синтаксис:

public event <Имя Делегата> <Имя события>

{

add {…}

remove {…}

}

Оба метода должны быть реализованы, при этом для хранения делегатов используется некоторое хранилище. Именно так реализованы классы для большинства интерфейсных объектов, использующие хэш-таблицы для хранения делегатов.

Давайте построим небольшой пример, демонстрирующий такой способ объявления и работы с событиями. Вначале построим класс с несколькими событиями:

class ManyEvents

{

//хэш таблица для хранения делегатов

Hashtable DStore = new Hashtable();

public event EventHandler Ev1

{

add

{

DStore["Ev1"]= (EventHandler)DStore["Ev1"]+ value;

}

remove

{

DStore["Ev1"]= (EventHandler)DStore["Ev1"]- value;

}

}

public event EventHandler Ev2

{

add

{

DStore["Ev2"]= (EventHandler)DStore["Ev2"]+ value;

}

remove

{

DStore["Ev2"]= (EventHandler)DStore["Ev2"]- value;

}

}

public event EventHandler Ev3

{

add

{

DStore["Ev3"]= (EventHandler)DStore["Ev3"]+ value;

}

remove

{

DStore["Ev3"]= (EventHandler)DStore["Ev3"]- value;

}

}

public event EventHandler Ev4

{

add

{

DStore["Ev4"]= (EventHandler)DStore["Ev4"]+ value;

}

remove

{

DStore["Ev4"]= (EventHandler)DStore["Ev4"]- value;

}

}

public void SimulateEvs()

{

EventHandler ev = (EventHandler) DStore["Ev1"];

if (ev!= null) ev(this, null);

ev = (EventHandler) DStore["Ev3"];

if (ev!= null) ev(this, null);

}

}// class ManyEvents

В нашем классе четыре события и хэш-таблица DStore для их хранения. Все события принадлежат встроенному классу EventHandler. Когда к событию будет присоединяться обработчик, автоматически будет вызван метод add, который динамически создаст элемент хэш-таблицы, Ключом элемента является в данном случае строка с именем события. При отсоединении обработчика будет исполняться метод remove, выполняющий аналогичную операцию над соответствующим элементом хэш-таблицы. В классе определен также метод SimulateEvs, при вызове которого зажигаются два из четырех событий – Ev1 и Ev3.

Рассмотрим теперь класс ReceiverEvs, слушающий события. Этот класс построен по описанным ранее правилам. В нем есть ссылка на класс, создающий события; конструктор с параметром, которому передается реальный объект такого класса; четыре обработчика события – по одному на каждое событие, и метод OnConnect, связывающий обработчиков с событиями. Вот код класса:

class ReceiverEvs

{

private ManyEvents manyEvs;

public ReceiverEvs(ManyEvents manyEvs)

{

this. manyEvs = manyEvs;

OnConnect();

}

public void OnConnect()

{

manyEvs.Ev1 += new EventHandler(H1);

manyEvs.Ev2 += new EventHandler(H2);

manyEvs.Ev3 += new EventHandler(H3);

manyEvs.Ev4 += new EventHandler(H4);

}

public void H1(object s, EventArgs e)

{

Console.WriteLine("Событие Ev1");

}

public void H2(object s, EventArgs e)

{

Console.WriteLine("Событие Ev2");

}

public void H3(object s, EventArgs e)

{

Console.WriteLine("Событие Ev3");

}

public void H4(object s, EventArgs e)

{

Console.WriteLine("Событие Ev4");

}

}// class ReceiverEvs

Тестирующая процедура состоит из нескольких строчек, в которых создаются нужные объекты, и запускается метод Simulate, зажигающий события:

public void TestManyEvents()

{

ManyEvents me = new ManyEvents();

ReceiverEvs revs = new ReceiverEvs(me);

me.SimulateEvs();

}

Все работает предусмотренным образом.

Проект «Город и его службы»

Завершить лекцию о событиях хочется содержательным учебным проектом, в котором моделируется жизнь города, происходящие в нем события, реакция городских служб на эти события. Наша главная цель в данном проекте еще раз показать, как возникающее событие, в данном случае – пожар в одном из домов города, обрабатывается по-разному городскими службами – пожарниками, милицией, скорой помощью. Конечно, все упрощено, в реальном городе событиями являются не только пожары и преступления, но и более приятные ситуации: день города,, открытие фестивалей и выставок, строительство новых театров и институтов.

Начнем с описания класса, задающего наш город. Этот класс уже появлялся и в этой и в предыдущей лекции, здесь его описание будет расширено. Начнем со свойств класса:

 

public class NewTown

{

//свойства

private int build, BuildingNumber; //дом и число домов в городе

private int day, days; //Текущий день года

//городские службы

private Police policeman;

private Ambulance ambulanceman;

private FireDetect fireman;

//события в городе

public event FireEventHandler Fire;

//моделирование случайных событий

private Random rnd = new Random();

//вероятность пожара в доме в текущий день: p= m/n

private int m = 3, n= 10000;

В нашем городе есть дома, есть время, текущее день за днем, городские службы, событие «пожар», которое, к сожалению, может случайно с заданной вероятностью возникать каждый день в каждом доме. Рассмотрим конструктор объектов нашего класса:

//конструктор класса

public NewTown(int TownSize, int Days)

{

BuildingNumber = rnd.Next(TownSize);

days = Days;

policeman = new Police(this);

ambulanceman= new Ambulance(this);

fireman= new FireDetect(this);

policeman.On();

ambulanceman.On();

fireman.On();

}

При создании объектов этого класса задается размер города – число его домов и период времени, в течение которого будет моделироваться жизнь города. При создании объекта создаются его службы – объекты соответствующих классов Police, Ambulance, FireDetect, которым предается ссылка на сам объект «город». При создании служб, вызываются методы On, подключающие обработчики события Fire каждой из этих служб к событию.

В соответствие с ранее описанной технологией определим метод OnFire, включающий событие:

protected virtual void OnFire(FireEventArgs e)

{

if (Fire!= null)

Fire(this, e);

}

Где и когда будет включаться событие Fire? Напишем метод, моделирующий жизнь города, где для каждого дома каждый день будет проверяться, а не возник ли пожар, и, если это случится, то будет включено событие Fire:

public void LifeOurTown()

{

for (day = 1; day<=days; day++)

for (build =1; build <= BuildingNumber; build++)

{

if (rnd.Next(n) <=m) //загорелся дом

{

//аргументы события

FireEventArgs e = new FireEventArgs(build, day, true);

OnFire(e);

if (e.Permit)

Console.WriteLine("Пожар потушен!" +

" Ситуация нормализована.");

else Console.WriteLine("Пожар продолжается." +

" Требуются дополнительные средства!");

}

}

}

Рассмотрим теперь классы receiver, обрабатывающие событие Fire. Их у нас три, по одному на каждую городскую службу. Все три класса устроены по одному образцу. Напомню, каждый такой разумно устроенный класс, кроме обработчика события имеет конструктор, инициализирующий ссылку на объект, создающий события, методы подключения и отсоединения обработчика от события. В такой ситуации целесообразно построить вначале абстрактный класс Receiver, в котором будет предусмотрен обработчик события, но не задана его реализация, а затем для каждой службы построить класс потомок. Начнем с описания родительского класса:

public abstract class Receiver

{

private NewTown town;

public Receiver(NewTown town)

{ this. town = town;}

public void On()

{

town.Fire += new FireEventHandler(It_is_Fire);

}

public void Off()

{

town.Fire -= new FireEventHandler(It_is_Fire);

town = null;

}

public abstract void It_is_Fire(object sender, FireEventArgs e);

}//class Receiver

Для классов потомков абстрактный метод It_is_Fire будет определен. Вот их описания:

public class Police: Receiver

{

public Police (NewTown town): base (town){}

public override void It_is_Fire(object sender, FireEventArgs e)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й." +

" Милиция ищет виновных!", e.Build,e.Day);

e.Permit &= true;

}

}// class Police

public class FireDetect: Receiver

{

public FireDetect (NewTown town): base (town){}

public override void It_is_Fire(object sender, FireEventArgs e)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й." +

" Пожарники тушат пожар!", e.Build,e.Day);

Random rnd = new Random(e.Build);

if (rnd.Next(10) >5)

e.Permit &= false;

else e.Permit &= true;

}

}// class FireDetect

public class Ambulance: Receiver

{

public Ambulance(NewTown town): base (town){}

public override void It_is_Fire(object sender, FireEventArgs e)

{

Console.WriteLine("Пожар в доме {0}. День {1}-й." +

" Скорая спасает пострадавших!", e.Build,e.Day);

e.Permit &= true;

}

}// class Ambulance

Для каждого потомка задан конструктор, вызывающий базовый метод родителя. Каждый потомок по-своему определяет обработчика события Fire. Обратите внимание на то, как в данном проекте решается проблема с выходным параметром события – Permit. Принята следующая стратегия – возвращаемое значение Permit будет истинно, если все обработчики согласны с этим. Поэтому каждый обработчик использует конъюнкцию выработанного им значения со значением, пришедшим от предыдущего обработчика. В данном примере все зависит от пожарников, которые могут вырабатывать разные решения.

Для полноты картины необходимо показать, как выглядит класс, задающий аргументы события, который, как и положено, является потомком класса EventArgs:

public class FireEventArgs: EventArgs

{

private int build;

private int day;

private bool permit;

public int Build

{

get { return (build);}

//set{ build = value;}

}

public int Day

{

get { return (day);}

//set{ day = value;}

}

public bool Permit

{

get { return (permit);}

set { permit = value;}

}

public FireEventArgs(int build, int day, bool permit)

{

this. build = build; this. day = day; this. permit = permit;

}

}// class FireEventArgs

Входные параметры события – build и day защищены от обработчиков события, а корректность выходного параметра гарантируется тщательным программированием самих обработчиков.

Для завершения проекта нам осталось определить тестирующую процедуру в классе Testing, создающую объекты и запускающую моделирование жизни города:

public void TestLifeTown()

{

NewTown sometown = new NewTown(100,100);

sometown.LifeOurTown();

}

Результаты ее работы зависят от случайностей. Вот как выглядит один из экспериментов:

Рис. 21.3. События в жизни города

Вариант 1

58. Обработчик события:

q всегда принадлежит классу, зажигающему событие;

q никогда не принадлежит классу, зажигающему событие;

q может принадлежать классу, зажигающему событие;

q принадлежит только одному классу, слушающему событие;

q может принадлежать многим классам, слушающим события.

59. Отметьте истинные высказывания:

q все события имеют одинаковую сигнатуру из двух аргументов с одними и теми же типами;

q все события имеют сигнатуру из двух аргументов, но с отличающимися типами;

q все события, не имеющие собственных аргументов, передаваемых обработчику, соответствуют стандартному встроенному делегату EventHandler;

q для класса с большим числом событий целесообразно применять динамическое связывание;

q для связывания событий с обработчиком можно применять только операцию +=.

60. Объявление события в классе может представлять собой:

q объявление метода класса;

q объявление поля класса;

q объявление процедуры-свойства класса с методами get и set;

q объявление процедуры-свойства класса с методами add и remove.

Вариант 2

62. Объекты одного класса:

q имеют одинаковый набор событий;

q в процессе вычислений зажигают одинаковый набор событий;

q в процессе вычислений зажигают набор событий, характерный для объекта;

q в разных сеансах работы зажигают одинаковый набор событий;

q в разных сеансах работы зажигают разные события.

63. Отметьте истинные высказывания:

q класс, обрабатывающий событие, может отсоединить других обработчиков события и полностью определить реакцию на событие;

q каждый объект определяет, какой обработчик присоединяется к событию;

q метод GetInvocationList не применим к событиям;

q все события имеют одну и ту же сигнатуру.

64. Класс EventHandler:

q является классом, которому принадлежат все события;

q является абстрактным классом и не может иметь экземпляров;

q является родительским классом для событий, не имеющих собственных аргументов;

q является потомком класса Delegate;

q накладывает ограничения на сигнатуру события.

Вариант 3

59. Процедура, в которой зажигается событие:

q передает обработчику событий входные аргументы и продолжает свою работу;

q передает обработчику событий входные аргументы и ждет окончания работы первого обработчика, связанного с событием;

q передает обработчику событий входные аргументы и ждет окончания работы всего списка обработчиков, связанного с событием;

q может получить от обработчиков выходные аргументы и использовать их в своей работе.

60. Отметьте истинные высказывания:

q события – это специализированный вариант делегатов;

q к каждому событию должен быть присоединен хотя бы один обработчик события;

q методы Combine и Remove, наследованные от класса Delegate не применимы к событиям;

q допустимо динамическое связывание событий с обработчиками событий.

61. Все аргументы события принадлежат:

q классу EventArgs;

q классу EventArgs, за исключением аргумента, задающего объект, зажигающий событие;

q потомку класса EventArgs;

q потомку класса EventArgs за исключением аргумента, задающего объект, зажигающий событие;

q возможно разным классам.

Лекция 22. Универсальность. Классы с родовыми параметрами

Наследование и универсальность – взаимно дополняющие базовые механизмы создания семейства классов. Родовые параметры универсального класса. Синтаксис универсального класса. Родовое порождение экземпляров универсального класса. Методы с родовыми параметрами. Ограниченная универсальность – ограничения, накладываемые на родовые параметры. Виды ограничений. Ограничение универсальности – это свобода действий. Примеры. Родовые параметры и частные случаи классов: структуры, интерфейсы, делегаты. Универсальность и Framework.Net.

Ключевые слова: свопинг; фактический тип; универсальность (genericity); универсальный класс (generic class). шаблон (template); типовые параметры; наследование; универсальность; стек; массив фиксированного размера; линейная односвязная списковая структура; поля; конструктор класса; родовое порождение экземпляров; ограничение безопасностью; ограничение наследования; ограничение конструктора; ограничение value/reference; универсальные структуры; универсальные интерфейсы; универсальные делегаты.

Наследование и универсальность

Необходимость в универсализации возникает с первых шагов программирования. Одна из первых процедур, появляющихся при обучении программированию, – это процедура свопинга – обмен значениями двух переменных одного типа. Выглядит она примерно так:

public void Swap(ref T x1, ref T x2)

{

T temp;

temp = x1; x1 = x2; x2 = temp;

}

Если тип T – это вполне определенный тип, например int, string или Person, то никаких проблем не возникает, все совершенно прозрачно. Но как быть, если возникает необходимость обмена данными разного типа? Неужели нужно писать копии этой процедуры для каждого типа? Проблема легко решается в языках, где нет контроля типов, – там достаточно иметь единственный экземпляр такой процедуры, прекрасно работающий, но лишь до тех пор, пока передаются аргументы одного типа. Когда же процедуре будут переданы фактические аргументы разного типа, то немедленно возникнет ошибка периода выполнения, и это слишком дорогая плата за универсальность процедуры.

В типизированных языках, не обладающих механизмом универсализации, выхода практически нет – приходится писать многочисленные копии Swap.

До недавнего времени Framework.Net и соответственно язык C# не поддерживали универсальность. Так что те, кто работает с языком C#, входящим в состав Visual Studio 2003 и ранних версий, должны смириться с отсутствием универсальных классов. Но в новой версии Visual Studio 2005, носящей кодовое имя Whidbey, проблема решена, и программисты получили наконец долгожданный механизм универсальности. Я использую в примерах этой лекции бета-версию Whidbey.

Замечу, что хотя меня, прежде всего, интересовала реализация универсальности, но и общее впечатление от Whidbey самое благоприятное.

Для достижения универсальности процедуры Swap следует рассматривать тип T как ее параметр, такой же, как и сами аргументы x1 и x2. Суть универсальности в том, чтобы в момент вызова процедуры передавать ей не только фактические аргументы, но и их фактический тип.

Под универсальностью (genericity) понимается способность класса объявлять используемые им типы как параметры. Класс с параметрами, задающими типы, называется универсальным классом (generic class). Терминология не устоялась и синонимами термина «универсальный класс» являются термины: родовой класс, параметризованный класс, класс с родовыми параметрами. В языке С++ универсальные классы называются шаблонами (template).

Синтаксис универсального класса

Объявить класс C# универсальным просто, для этого достаточно указать в объявлении класса, какие из используемых им типов являются параметрами. Список типовых параметров класса, заключенный в угловые скобки, добавляется к имени класса:

class MyClass<T1, … Tn> {…}

Как и всякие формальные параметры, Ti являются именами (идентификаторами). В теле класса эти имена могут задавать типы некоторых полей класса, типы аргументов и возвращаемых значений методов класса. В некоторый момент (об этом скажем чуть позже) формальные имена типов будут заменены фактическими параметрами, представляющими уже конкретные типы – имена встроенных классов, классов библиотеки FCL, классов, определенных пользователем.

В C# универсальными могут быть как классы, так и все их частные случаи – интерфейсы, структуры, делегаты, события.

Класс с универсальными методами

Специальным частным случаем универсального класса, является класс, не объявляющий сам параметров, но разрешающий делать это своим методам. Давайте начнем рассмотрение универсальности с этого частного случая. Вот как выглядит класс, содержащий универсальный метод swap:

class Change

{

static public void Swap<T>(ref T x1, ref T x2)

{

T temp;

temp = x1; x1 = x2; x2 = temp;

}

}

Как видите, сам класс в данном случае не имеет родовых параметров, но зато универсальным является статический метод класса swap, имеющий родовой параметр типа T. Этому типу принадлежат аргументы метода и локальная переменная temp. Всякий раз при вызове метода ему наряду с фактическими аргументами будет передаваться и фактический тип, заменяющий тип T в описании метода. О некоторых деталях технологии подстановки и выполнения метода поговорим в конце лекции, сейчас же отмечу, что реализация вызова универсального метода в C# не приводит к существенным накладным расходам.

Рассмотрим тестирующую процедуру из традиционного для наших примеров класса Testing, в которой интенсивно используется вызов метода swap для различных типов переменных:




Поделиться с друзьями:


Дата добавления: 2014-12-25; Просмотров: 455; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.185 сек.