C# - 何时调用 base.在某事上

本文关键字:base 何时 调用 | 更新日期: 2023-09-27 18:32:36

我正在使用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方法的经验法则是什么?

C# - 何时调用 base.在某事上

仅当

该 API 的文档指定应调用base.SomeVirtualMethod时,才需要调用该。否则,应将其隐含为可选。需要您调用基方法但没有明确说明的 API 设计得很糟糕。

需要基调用的

原因是设计不佳,因为您永远无法期望重写您的方法的人会做什么,并且您无法确定他们会调用基方法来执行任何必需或关键代码。

所以简而言之,请参阅文档,否则通常没有必要。.NET Framework 就是根据这样的准则设计的,由于这些原因,大多数虚拟方法不需要调用 base。那些这样做是有记录的。

感谢roken指出调用base虚拟方法的一个非常重要的原因,就是在使用事件时。但是,我的反驳论点是,情况并非总是如此,仍然适用,特别是如果您使用的是不遵循 .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 的三次调用。如果你注释掉DerivedFoo中对base.OnLoad的调用,你现在只会得到一个对DerivedFoo.OnLoad的调用,并且基和订阅者没有被调用。

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

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

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

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

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

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

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

作为 WinForms 中的"经验法则",使用 On[EventName](即 OnFormClosesing(方法时,必须调用基方法才能由框架类触发相应的事件(否则控件不会引发该事件(。无论设计是否糟糕,这是一种非常常见的模式。

一般来说,你最好先调用基方法:它配置一个类。然后运行自己的逻辑。

例如:当你重写OnSelectedItemChanged - 你调用基方法时,它会将你的类切换到正确的条件,然后你可以做你想做的事(对新的选定项做一些事情(。

因此,了解基本方法中发生了什么会很有用。也许你不需要打电话。

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

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

需要考虑的一种特殊情况:如果您使用的是Dispose(bool(习惯用法,则必须调用base。清理完自己的资源后处置(布尔值(。

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