3

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

那么,问题来了——在哪些情况下调用顺序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在这些情况下,选择正确位置调用方法的经验法则是什么?

4

4 回答 4

3

您只需要在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.OnLoadin的调用DerivedFoo,您现在只会获得一次对 的调用DerivedFoo.OnLoad,并且不会调用 base 和订阅者。

这一点仍然很重要,它取决于文档。仍然不能确定基本虚拟方法实现是否调用其订阅者。所以这应该很清楚。幸运的是,多亏了框架设计者,.NET 框架与 .NET 事件模型非常一致,但我仍然无法强调总是阅读 API 的文档。

当您根本不处理事件而是处理抽象基类之类的事情时,它会发挥很大作用。您如何知道是否为抽象类调用基事件?抽象类是否提供默认实现,还是希望您提供它?

文档是为虚拟会员定义合同的最有力、最清晰的方式。这就是 .NET 框架设计团队通常为交付的抽象类提供至少一个具体实现的原因之一。

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

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

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

我希望这能消除我在评论中的一些困惑。

于 2012-04-30T10:07:10.750 回答
3

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

于 2012-04-30T11:13:24.383 回答
2

In general, you'd better to call the base method first : it configures a class. Then you run your own logic.

For example: When you override OnSelectedItemChanged - you call base method, it switches you class to the right condition, and then you can do what you want (do something whith a new selected item).

So, it will be useful to know what's going on in base method. Maybe you don't need to call it.

How to choose: just check the class in DotPeek and see if you really need to call base method.

When it matter: The base method can override your changes. And you'll get strange behaviour.

于 2012-04-30T09:33:53.777 回答
2

需要考虑的一种特殊情况:如果您使用 Dispose(bool) 习惯用法,则必须在清理自己的资源后调用 base.Dispose(bool)。

(我认为这与 Windows.Forms 相关,因为它们使用 Dispose(bool) 习语)

于 2012-04-30T10:15:06.240 回答