C# 我可以依赖基类的事件执行顺序来避免不必要的虚拟成员吗?

C# 我可以依赖基类的事件执行顺序来避免不必要的虚拟成员吗?,c#,event-handling,derived-class,C#,Event Handling,Derived Class,有时,我希望我的派生类在通知任何其他订阅之前对某个基类事件做出反应并更改状态 我是否需要为每个事件先复制受保护的虚拟void handlex(…),还是可以依赖这样的事件执行顺序 public class BaseClass { public event X EventA; void RaiseA(...) { if (EventA != null) EventA(this, ...); } ... } public class Der

有时,我希望我的派生类在通知任何其他订阅之前对某个基类事件做出反应并更改状态

我是否需要为每个事件先复制受保护的虚拟void handlex(…),还是可以依赖这样的事件执行顺序

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

    ...
}

public class Derived : BaseClass
{
    public Derived()
    {
        EventA += ...
    }
}
受保护的虚拟
”方法是:

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(...)
    {
        ...
    }
}

答案取决于你想要什么样的保证

构造函数在字段初始值设定项之后执行,基类构造函数在派生类构造函数之前执行。因此,正如您自己的
派生的
类在其构造函数中向事件添加订阅者一样,基类也可以在您之前添加订阅者

更糟糕的是,虽然基类构造函数肯定不赞成这样做,但没有什么可以阻止它将
传递给其他代码,这样它就有机会在
派生的
类之前订阅事件

最后,只有实现事件的类才能完全控制事件处理程序的实际执行顺序。通常,它只调用表示事件的委托实例,该实例将按照订阅的顺序执行每个处理程序。但是没有什么可以阻止事件的实现者以其他方式维护和使用事件订阅

例如,它可以显式地实现事件,并执行类似
eventField=value+eventField的操作add
方法中的code>(在订阅时反转订阅),或者在引发事件时,显式枚举事件委托对象的调用列表,并以相反顺序调用处理程序

当然,所有这些场景都应该非常罕见,尤其是最后一个场景(这真的很奇怪)。偏离正常事件实现的代码是并且应该是非常罕见的。但是你能保证代码不是那样的吗?并非没有代码编写者的承诺(即使在那里,人们也容易犯错,可能会忘记或意外地违背他们的承诺)

因此,如果您想要一个铁一般的保证,即您的代码将在任何其他类之前执行,那么您需要将自己的类密封起来(这样其他人就无法覆盖事件引发方法),并在调用方法的基类实现之前通过重写实际引发事件的虚拟方法进行处理


即使在那里,您也依赖于一个隐含的承诺,即事件不会通过任何其他机制引发。但这是一个很好的保证。

我的问题更多的是关于我同时控制基类和派生类的情况。看起来我可以毫无顾虑地订阅派生类构造函数中的事件(因为我知道我在基类中没有做任何添加/删除操作)。客户端(同时订阅同一事件)只有在派生类处理该事件后才会收到该事件。是的,如果您对基类构造函数的实现非常小心(包括不调用其他代码,这些代码将看到您的
this
引用),您应该可以。尽管我忍不住想知道,不遵循正常的.NET习惯用法的具体原因是什么,简单地将
RaiseA()
方法虚拟化(在习惯用法中,这通常被称为
OnA()
),并且如果派生类想要拦截/预处理/等等,则重写派生类。“将RaiseA()方法虚拟化”-我不使用RaiseA,而是在事件上直接使用扩展方法Raise。当然,只有当我确定不需要从派生类引发基类事件时,我才能这样做。啊,好的。我明白了…从给出的代码示例中,我不太清楚这一点。是的,如果您还没有用于引发特定事件的虚拟方法,那么我想在派生类构造函数中订阅该事件是一个合理的选择,只要您小心。