C# – 什么时候打电话给base.OnSomething?

我正在使用Windows.Forms并且必须inheritance一些控件来提供自定义行为。 这种inheritance显然会导致方法覆盖。

所以,这里有一个问题 – 在哪种情况下调用base.OnSomething(...)的顺序可以真正影响程序的可见行为?

 protected override OnRetrieveVirtualItem(RetrieveVirtualItemEventArgs e) { // base.OnRetrieveVirtualItem(e); - Could this potentially break something? // Perform your custom method. // ... base.OnRetrieveVirtualItem(e); // Is this always "correct"? } 

据我所知,当重写与绘画相关的方法( OnDrawItem, ... )时,这个顺序很重要,但我想还有其他一些方法可以在腿上拍摄自己,因为Windows.Forms会做很多事情。非托管代码调用可能的副作用。

那么,什么时候可能重要? 在这些情况下,选择正确的位置来调用base方法有哪些经验法则?

当该API的文档指定您应该这样做时,您只需要调用base.SomeVirtualMethod 。 否则,它应该暗示为可选的。 需要您调用基本方法的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.OnLoadDerivedFoo.OnLoad和事件订阅dFoo.Load三次调用。 如果您在base.OnLoadDerivedFoobase.OnLoad的调用,那么现在只能对DerivedFoo.OnLoad进行一次调用,并且不会调用基类和订阅者。

关于文档的问题仍然很强烈。 仍然不确定基本虚拟方法实现是否调用其订户。 所以这应该是清楚的。 幸运的是,由于框架设计人员,.NET Framework与.NET事件模型非常一致,但我仍然不能强调总是阅读API的文档。

当你根本不处理事件时会发生很多事情,比如抽象基类。 你怎么知道是否为抽象类调用基本事件? 抽象类是提供默认实现,还是希望您提供它?

文档是为虚拟成员定义合同的最强大,最清晰的方式。 这就是为什么.NET框架设计器团队通常为一个抽象类提供至少一个具体实现的原因之一。

我认为Krzysztof Cwalina在框架设计指南中说得最好。

我得到的一个常见问题是虚拟成员的文档是否应该说覆盖必须调用基本实现。 答案是覆盖应该保留基类的契约。 他们可以通过调用基本实现或通过其他方式来实现。 成员很少声称保留其合同(在覆盖中)的唯一方法是调用它。 在很多情况下,调用基数可能是保留合同的最简单方法(文档应该指出这一点),但它很少是绝对必需的。

我完全同意。 如果您覆盖基本实现并决定不调用它,则应提供相同的function。

我希望这能澄清我在评论中的一些困惑。

作为WinForms中的“经验法则”,使用On [EventName](即OnFormClosing)方法,您必须调用基本方法,以便框架类触发相应的事件(否则事件不会由控制)。 不好的设计与否,这是一种非常常见的模式。

通常,您最好先调用基本方法:它配置一个类。 然后你运行自己的逻辑。

例如:当您重写OnSelectedItemChanged – 您调用基本方法,它会将您的类切换到正确的条件,然后您可以执行您想要的操作(执行新选择的项目)。

因此,知道基本方法中发生了什么将是有用的。 也许你不需要打电话。

如何选择:只需检查DotPeek中的类,看看你是否真的需要调用基本方法。

什么时候重要:基本方法可以覆盖您的更改。 你会得到奇怪的行为。

一个需要考虑的特殊情况:如果你使用Dispose(bool)习语,你必须在清理自己的资源之后调用base.Dispose(bool)。

(这与Windows.Forms有关,我认为,因为他们使用Dispose(bool)成语)