您只需要致电base.SomeVirtualMethod
当该 API 的文档指定您应该这样做时。否则,它应该被暗示为可选的。要求您调用基本方法但没有明确声明的 API 设计得很糟糕。
需要基调用的原因是糟糕的设计,因为您永远无法期望覆盖您的方法的人会做什么,并且您无法确定他们会调用基方法来执行任何必需或关键代码。
简而言之,请参阅文档,否则通常没有必要。 .NET Framework 是根据此类准则设计的,因此大多数虚拟方法不需要调用基类。那些做的事情都有记录。
感谢 roken,他指出了调用基本虚方法的一个非常重要的原因,就是在使用事件时。但是,我的反驳观点(情况并非总是如此)仍然适用,特别是如果您使用不遵循 .NET 习惯用法和模式的第三方库或类,则没有任何确定性。以这个例子为例。
namespace ConsoleApplication12
{
using System;
using System.Diagnostics;
class Foo
{
public Foo() {
}
public event EventHandler Load;
protected virtual void OnLoad() {
EventHandler handler = Load;
if (handler != null) {
handler(this, new EventArgs());
}
Debug.WriteLine("Invoked Foo.OnLoad");
}
public void Run() {
OnLoad();
}
}
class DerivedFoo : Foo
{
protected override void OnLoad() {
base.OnLoad();
Debug.WriteLine("Invoked DerivedFoo.OnLoad");
}
}
class Program
{
static void Main(string[] args) {
DerivedFoo dFoo = new DerivedFoo();
dFoo.Load += (sender, e) => {
Debug.WriteLine("Invoked dFoo.Load subscription");
};
dFoo.Run();
}
}
}
如果运行此示例,您将获得三个调用Foo.OnLoad
, DerivedFoo.OnLoad
,以及事件订阅dFoo.Load
。如果您注释掉对base.OnLoad
in DerivedFoo
,您现在只会获得一次调用DerivedFoo.OnLoad
,并且基地和订户没有接到电话。
这一点仍然很重要,那就是取决于文档。仍然不确定基本虚拟方法实现是否调用其订阅者。所以这一点应该很清楚。幸运的是,由于框架设计者的帮助,.NET 框架与 .NET 事件模型非常一致,但我仍然无法强调要始终阅读 API 文档。
当您根本不处理事件而是处理抽象基类之类的事情时,它会发挥很大的作用。如何知道是否调用抽象类的基事件?抽象类是否提供默认实现,或者是否期望您提供它?
文档是定义虚拟会员合同的最有力、最清晰的方式。这就是 .NET 框架设计团队通常为交付的抽象类提供至少一个具体实现的原因之一。
我认为 Krzysztof Cwalina 在框架设计指南中说得最好。
我遇到的一个常见问题是虚拟成员的文档是否应该说明重写必须调用基本实现。答案是重写应该保留基类的约定。他们可以通过调用基本实现或其他方式来完成此操作。很少有成员可以声称保留其合同(在覆盖中)的唯一方法是调用它。在很多情况下,调用基础可能是保留合同的最简单方法(文档应该指出这一点),但这很少是绝对必需的。
我完全同意。如果您覆盖基本实现并决定不调用它,您应该提供相同的功能。
我希望这能澄清我在评论中遇到的一些困惑。