Воскресенье, 09.05.2021, 19:32
Приветствую Вас Гость | RSS
Авторизация
Статистика


Rambler's Top100


Яндекс.Метрика
Контакты
356118831
Mr_Ser_Win
Поиск
Реклама

Книжный портал

Каталог файлов

Главная » Файлы » Программирование » Книги C#

[C#] Виртуальные события
21.04.2011, 12:14

[C#] Виртуальные события

Вы когда-нибудь задавались вопросом о том, могут ли события быть виртуальными? Вполне возможно вас самих эта мысль никогда не посещала; возможно, вам в этом помог какой-нибудь дотошный парень на собеседование, в общем, не очень-то важно, думали вы об этом или нет, давайте подумаем над этим вопросом совместно прямо сейчас.

Итак, события в языке C# по сути являются реализацией известного паттерна publish/subscribe и содержат всего пару методов add и remove, для подписки и отписки от события, и закрытое поле с мультикаст делегатом, который, собственно, этих самых подписчиков и содержит. А раз событие – это по сути методы, а методы могут быть виртуальными, можно сделать вывод, что события тоже могут быть виртуальными (тем более что свойства ведь могут быть виртуальными и этот факт не вредит ничьему ментальному здоровью). Итак, теоретически – с виртуальными событиями все должно быть нормально, однако это как раз тот случай, когда теория с практикой несколько расходятся.

Но обо всем по порядку; давайте рассмотрим следующий пример. Предположим у вас есть некоторый базовый класс, скажем, класс Base, который содержит событие с именемSomeEvent, и есть производный класс Derived, который по какой-то причине хочет иметь возможность переопределить событие базового класса. Вполне возможно, что причина столь необычной идеи уходит корнями к безумной преданности вашего высокодушевного сознания к замечательной книге банды четырех, которую вы перечитываете длинными зимними вечерами. Так вот, в очередной раз перечитывая паттерны поведения вы осознали, что к событиям вполне можно применить паттерн «Метод шаблона» сделав непосредственный процесс подписки/отписки виртуальным, а процесс генерации события – невиртуальным, таким образом четко разделив ответственность между базовым классом и его наследником.

В результате, это идея вылилась в следующий код:

// Базовый класс с виртуальным событием. Реализация паттерна проектирования
// "Метод шаблона", когда само событие является виртуальным и переопределяется
// наследником, а процесс вызова события реализован с помощью открытого
// невиртуального метода
class Base
{
    // Само вирутуальное событие
    public virtual event EventHandler SomeEvent;

    // Фукнция генерации события
    // (сделана открытой только в демонстрационных целях)
    public void InvokeSomeEvent()
    {
        var handler = SomeEvent;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
}

// Класс-наследник, переопределяющий виртуальное событие
class Derived : Base
{
    // Переопределяем виртуальное событие
    public override event EventHandler SomeEvent;
}

class Program
{
    static void Main(string[] args)
    {
        Derived derived = new Derived();
        derived.SomeEvent += (s, e) => Console.WriteLine("Some event handler.");
        derived.InvokeSomeEvent();
        Console.ReadLine();
    }
}

Запускам этот код на выполнение и … не видим в консоли ничего, поскольку наш обработчик события не вызывается.

Все дело в том, что событие (field-like event) в базовом классе Base все также разворачивается в пару открытых методов add/remove и *закрытое* (private) поле делегата с типомEventHandler, который модифицируется в обозначенных выше методах. И когда компилятор встречает имя такого события в коде, то он трактует его по разному, в зависимости от того, где находится это обращение: «снаружи» этого класса или «внутри» него. Если обращение к событию происходит внутри класса, то ты вместо события как такового мы обращаемся к нижележащему делегату, а если же обращение происходит снаружи класса – то мы «видим» наше событие через «призму» двух методов подписки/отписки. Это сделано специально для разграничения ответственности: внешний код может лишь подписываться и отписываться на событие, а вот «зажигать» событие или его модифицировать напрямую (установив его в null, например) – не может. Но проблема заключается в том, что в нашем случае то же самое происходит и в классе Derived: для него также генерируется свое собственное закрытое поле  и его именно оно изменяется в классе Derived.

Таким образом, компилятор генерирует примерно следующий код:

class Base
{
    // Реализуем событие "вручную"
    public virtual event EventHandler SomeEvent
    {
        // Обращаемся к своему собственному закрытому полю
        add { _baseClassBackingDelegate += value; }
        remove { _baseClassBackingDelegate -= value; }
    }

    // Фукнция генерации события
    private void InvokeSomeEvent()
    {
        // Обращаемся к полю делегата, а не к имени события,
        // поскольку вызов делегата осуществляется именно через поле делегата
        var handler = _baseClassBackingDelegate;
        if (handler != null)
            handler(this, EventArgs.Empty);
    }

    // Поле делегата является закрытым, а не защищенным,
    // что делает невозможным подписку/описку именно к этому полю
    // из класса наследника
    private EventHandler _baseClassBackingDelegate;
}

class Derived : Base
{
    // Переопределяем событие
    public override event EventHandler SomeEvent
    {
        // Обращаемся к собственному закрытому полю
        add { _derivedClassBackingDelegate += value; }
        remove { _derivedClassBackingDelegate -= value; }
    }

    // В классе наследнике объявляется еще одно поле делегата
    private EventHandler _derivedClassBackingDelegate;
}

Теперь должно быть понятно, почему наше событие не было вызвано: наш метод обратного вызова из-за переопределения события в классе Derived был добавлен в поле делегата производного класса, а метод InvokeSomeEvent вызывает все методы обратного вызова, сохраненные в классе Base.

Так уж выходит, что наша попытка сделать виртуальное field-like событие с невиртуальным методом генерации этого события приводит именно туда, куда ведут многие другие благие намерения, а именно к трудноуловимым ошибкам и головной боли наших коллег, которые будут поддерживать этот код в будущем.  Основная идея наследования вообще и применение паттерна проектирования "Метод шаблона” в частности, направлены на то, чтобы сделать отношения между базовым классом и наследником простыми и понятными, такими, чтобы базовый класс было легко использовать правильно и сложно – неправильно. В случае же с виртуальными событиями сложно вообще понять, какая именно роль и обязанности возлагаются на базовый класс, а какая – на производный. И хотя использование виртуальных событий все же возможно (*) (если делать это осторожно и со знанием дела), я бы предпочел более четкий контракт между базовым классом и его наследниками в виде более конкретных и понятных виртуальных функций.

P.S. Нужно отметить, что хотя теоретически компилятор мог бы определять подобные ситуации и генерировать защищенное поле в базовом классе, но делать этого никто не будет по одной простой причине: подобные изменения будут ломающими (так называемые breaking changes). Просто представьте себе, что у кого-то в продакшне находится подобный код с виртуальным событием, но с дополнительной функцией InvokeSomeEvent в производном классе. Тогда, после перехода на новую версию языка C#, начнет вызываться дополнительный код, который до этого не вызывался и не тестировался, что может привести к непредсказуемому поведению приложения и или непонятным ошибкам во время выполнения.

АВТОР: СЕРГЕЙ ТЕПЛЯКОВ

Статья позаимствована с сайта: sergeyteplyakov.blogspot.com

Сообщить о битой ссылке
Категория: Книги C# | Добавил: Mr_Ser_Win | Теги: C#
Просмотров: 1603 | Загрузок: 0 | Комментарии: 2 | Рейтинг: 5.0/1
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Получить бонус WMR

Вы можете получить WMR-бонус в размере 0,01-0,10 WMR на свой кошелек 1 раз в сутки

Кошелек
Код Защитный код

Обмен Webmoney



Получить больше бонусов
Помощь сайту

Облако тегов
Реклама
Качественный хостинг
Бесплатная раскрутка сайтов и блогов - YouRaise.Ru
Начать Заработок на Блоге
Хранилище фотографий фото хостинг Храни фото здесь!
Драки: дом2 драки
обмен играми Все для геймера!
А ты играл в XBOX 360? продажа развивающие игры в твоем городе
AlfaInternet.Su - Регистрация сайта в каталогах поисковиках

Регистрация сайта в Каталогах
Ваше имя:
Ваш email:
Регистрация при поддержке AlfaInternet.Su
PR-CY.ru