Могу ли я рассчитывать на порядок выполнения событий для базовых классов, чтобы избежать ненужных виртуальных членов?

1

Иногда я хочу, чтобы мой производный класс реагировал на какое-то событие базового класса и менял состояние до того, как любые другие подписчики могут быть уведомлены.

Нужно ли дублировать protected virtual void HandleXBeforeOthers(...) для каждого события или я могу полагаться на порядок выполнения событий, как это?

public class BaseClass
{
    public event X EventA;
    void RaiseA(...)
    {
        if (EventA != null) EventA(this, ...);
    }

    ...
}

public class Derived : BaseClass
{
    public Derived()
    {
        EventA += ...
    }
}

Под " protected virtual " подходом будет:

public class BaseClass
{
    public event X EventA;
    void RaiseA(...)
    {
        HandleEventABeforeOthersCan(...);
        if (EventA != null) EventA(this, ...);
    }

    protected virtual void HandleEventABeforeOthersCan(...)
    {
        ...
    }

    ...
}

public class Derived : BaseClass
{
    protected override void HandleEventABeforeOthersCan(...)
    {
        ...
    }
}
Теги:
event-handling
derived-class

1 ответ

0
Лучший ответ

Ответ зависит от того, какую гарантию вы хотите.

Конструкторы выполняются после инициализаторов полей, а конструкторы базового класса выполняются перед конструкторами производных классов. Таким образом, так же, как ваш собственный класс Derived добавляет подписчика к событию в свой конструктор, так же может быть и базовый класс, сделанный до вашего.

Хуже того, хотя это определенно нахмурилось, нет ничего, что могло бы помешать конструктору базового класса передать this другому коде, где у него будет возможность подписаться на это событие, прежде чем ваш класс Derived сделает.

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

Например, он может реализовать событие явно и сделать что-то вроде eventField = value + eventField; в методе add (реверсивная подписка во время подписки) или, при поднятии события, явным образом перечисляю список вызовов для объекта делегирования события и вызывая обработчики в обратном порядке.

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

Поэтому, если вам нужно что-то близкое к гарантированному железом гарантированию того, что ваш код будет выполняться перед любым другим, вам нужно сделать свой собственный класс sealed (чтобы никто больше не мог переопределить метод создания событий), и выполняйте свою обработку перед вызовом реализация базового класса метода путем переопределения виртуального метода, который фактически вызывает событие.

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

  • 0
    Мой вопрос больше касается случая, когда я контролирую как базовые, так и производные классы. Похоже, тогда я могу подписаться на события в конструкторе производного класса без каких-либо забот (потому что я знаю, что я не делаю ничего сложного с добавлением / удалением в базовом классе). Клиент (который также подписывается на то же событие) получит событие только после того, как оно будет обработано производным классом.
  • 0
    Да, если вы осторожны с реализациями конструктора базового класса (в том числе не обращаетесь к другому коду, который бы затем увидел вашу ссылку this ), у вас все должно быть в порядке. Хотя я не могу не задуматься о том, в чем заключается конкретная причина того, что не следует обычной идиоме .NET, а просто делает метод RaiseA() виртуальным (в идиоме это обычно называется OnA() ) и переопределение производных классов если они хотели перехватить / препроцессировать / и т.д.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню