КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Явное управление регистрацией событийПорой методы add и remove, сгенерированные компилятором, далеки от идеала. Ранее я уже говорил о всех проблемах, связанных с безопасностью потоков, существующих в компиляторе C# компании Microsoft. Вообще-то компилятор C# компании Microsoft никогда не обеспечивает максимальную безопасность, если речь идет о программировании безопасного и надежного кода. Чтобы создавать исключительно надежные компоненты, рекомендую всегда использовать прием, описанный в этом разделе — он позволит решить все проблемы безопасности потоков. Впрочем, его можно использовать и для решения других задач. В частности, очень распространенная причина, побуждающая программистов к самостоятельной реализации методов add и remove, — определение в типе множества событий, когда одновременно требуется эффективнее использовать память. В следующем разделе мы разберем такой сценарий подробнее. К счастью, компилятор C#, как и многие другие, позволяет разработчикам явно реализовывать методы-аксессоры add и remove. Вот как можно модифицировать объект MailManager для обеспечения безопасной для потоков регистрации и отмены регистрации на уведомление о событии:
internal class MailManager {
// Создаем закрытое зкземплярное поле // для блокировки синхронизации потоков. private readonly Object m_eventLock = new Object(); // Создаем закрытое поле, ссылающееся на заголовок списка делегатов, private EventHandler<NewMailEventArgs> m_NewMail; // Создаем в классе член-событие. public event EventHandler<NewMailEventArgs> NewMail { // Явно реализуем метод add. add { // Берем закрытую блокировку и добавляем обработчик // (передаваемый по значению) в список делегатов, lock (m_eventLock) { m_NewMail += value; } } // Явно реализуем метод remove, remove { // Берем закрытую блокировку и удаляем обработчик // (передаваемый по значению) из списка делегатов, lock (m_eventLock) { m_NewMail -= value; } } } // Определяем метод, отвечающий за инициирование события // и информирование об этом зарегистрированных объектов. // Если класс изолирован, определяем метод как закрытый и невиртуальный. protected virtual void OnNewMail(NewMailEventArgs e) { // Сохраняем поле делегата во временном поле для обеспечения безопасности потоков. EventHandler<NewMailEventArgs> temp = m_NewMail; // Если есть зарегистрировавшиеся объекты, // уведомляем их. if (temp != null) temp(this, е); } // Создаем метод, преобразующий входную информацию // в требуемое событие. public void SimulateNewMail(String from, String to, String subject) { // Создаем объект, хранящий информацию, которую нужно передать // объектам, получающим уведомление о событии. NewMailEventArgs е = new NewMailEventArgs(from, to, subject); // Вызываем виртуальный метод, уведомляющий наш объект о возникновении события. // Если нет типа, переопределяющего этот метод, // наш объект уведомит все объекты, подписавшиеся // на уведомление о событии. OnNewMail(e); } }
В новой версии объекта MailManager закрытое поле m_NewMail, ссылающееся на список делегатов, должно определяться явно. В оригинальном синтаксисе события компилятор C# автоматически определял закрытое поле. В новом синтаксисе, в котором разработчик явно предоставляет реализацию методов-аксессоров add и remove, поля также должны объявляться явно. Это поле просто представляет собой ссылку на делегат EventHandler<NewMailEventArgs>. Нет ничего, что превращает это поле в событие. В новом расширенном синтаксисе после ключевого слова event следует то, что собственно определяет событие в типе. В блоках add и remove содержится реализация методов-аксессоров. Заметьте: каждый метод принимает скрытый параметр по имени value типа EventHandler<NewMailEventArgs>. Код внутри методов выполняет все операции по добавлению и удаление делегатов из списка. В отличие от свойств, у которых есть оба или хотя бы один метод-аксессор get или set, у событий всегда должны быть оба метода-аксессора add и remove. Явная реализация методов-аксессоров, показанная в предыдущем примере, работает как методы-аксессоры, сгенерированные компилятором C#, если не считать отсутствия атрибута [MethodImpl(MethodImplOptionsSynchronized)], а вместо этого используется оператор lock языка С# со ссылкой на закрытый определенный объект meventLock типа Object. Именно так я решаю проблему небезопасности потоков, о которой говорил в предыдущем разделе. Поскольку поле m_eventLock объявляется как закрытое, никакой код, за исключением кода класса MailManager, не в состоянии получить доступ к нему; за счет этого повышается надежность класса MailManager. Событие можно объявлять как статический член, отчего методы-аксессоры также становятся статическими. Естественно, что закрытое поле mJSIewMail также станет статическим. В таком случае для обеспечения безопасности потоков поле m_eventLock также нужно объявить статическим. Это применимо как к ссылочным, так и значимым типам, в которых требуется предоставить статическое событие безопасно для потоков. К сожалению, как говорилось в предыдущем разделе, не существует хорошего способа сделать события экземпляров значимого типа безопасными с точки зрения потоков из-за того, что нет хорошего способа инициализировать экземплярное поле в значимом типе. За объяснением причин отсылаю вас к главе 5. Код, регистрирующий и отменяющий регистрацию события, не различает методы add и remove, созданные автоматически компилятором или явно реализованные разработчиком. На самом деле это не исключает возможности использования в исходном тексте операторов += и -=, при этом компилятор будет знать, что следует сгенерировать вызовы явно определенных методов. И последнее замечание относительно метода OnNewMail. Семантически этот метод идентичен предыдущей версии. Единственная разница в том, что имя события (NewMail) заменено на имя поля делегата, m_NewMail.
|