为啥不在构造函数中调用可覆盖的方法?

Posted

技术标签:

【中文标题】为啥不在构造函数中调用可覆盖的方法?【英文标题】:Why do not call overridable methods in constructors?为什么不在构造函数中调用可覆盖的方法? 【发布时间】:2014-01-18 19:44:10 【问题描述】:

这是一个过于简单的例子,但我有一些现实生活中的代码在概念上做同样的事情(试图验证派生类的值“设置”访问器方法),分析器给我“不要在构造函数。”我试图弄清楚我是否应该更改我的代码,或者忽略警告。我想不出任何我应该听从警告的理由。

public abstract class SimpleUrl

    protected string _url;
    public abstract string Url  get; set; 
    public SimpleUrl()
     
    public SimpleUrl(string Url)
    
        this.Url = Url;
    


public class HttpUrl : SimpleUrl

    public HttpUrl()
     
    public HttpUrl(string Url)
    
        this.Url = Url;
    
    public override string Url
    
        get
        
            return this._url;
        
        set
        
            if (value.StartsWith("http://"))
                this._url = value;
            else
                throw new ArgumentException();
        
    

【问题讨论】:

原因是子类可能会在超类尚未初始化的那个方法中使用对象的属性,从而导致意外行为。 射击。一分钟前,我发布了一个答案,这是错误的。所以我删除了它。以防万一有人注意到这一点。 @EdwardNedHarvey 我注意到了,并且仍然可以看到它:-P 【参考方案1】:

作为 T.S.说(谢谢 T.S.),在实例化派生类型时,将始终调用基本构造函数。如果您像我在问题中所做的那样,派生构造函数没有明确指定要使用的基构造函数,则使用无参数基构造函数。

public HttpUrl()           // : base() is implied.

public HttpUrl(string Url) // : base() is still implied.  
                           // Parameterless. Not :base(Url)

所以这个问题的完整答案是:如果你密封派生类,在基类中声明的抽象或虚拟方法只能在基类中覆盖 .因此,要制作干净的代码,请不要在基本构造函数中运行此行:

this.Url = Url;   // Don't do this in the base constructor

相反,您应该“密封”派生类(或方法)。如下:

public abstract class SimpleUrl

    protected string _url;
    public abstract string Url  get; set; 
    // Optionally declare base constructors that do *not* call Url.set
    // Not sure why these constructors are optional in the base, but
    // required in the derivative classes.  But they are.
    public SimpleUrl()
     


public sealed class HttpUrl : SimpleUrl

    public HttpUrl()   // Not sure why this is required, but it is.
     
    public HttpUrl(string Url)
    
        // Since HttpUrl is sealed, the Url set accessor is no longer
        // overridable, which makes the following line safe.
        this.Url = Url;
    
    public override string Url
    
        get
        
            return this._url;
        
        set
        
            if (value.StartsWith("http://"))
                this._url = value;
            else
                throw new ArgumentException();
        
    

或者,您不需要密封整个派生类,如果您只是密封可覆盖的方法(使其不再被任何进一步的派生类覆盖)

public class HttpUrl : SimpleUrl

    // ...
    public override sealed string Url
    // ...

【讨论】:

【参考方案2】:

答案确实是,这可能导致意外行为。

http://msdn.microsoft.com/en-us/library/ms182331.aspx

您在代码中遗漏了一些东西:

public class HttpUrl : SimpleUrl

    public HttpUrl()**:base()**
     
    public HttpUrl(string Url)**:base(Url)**
    
        this.Url = Url;
    
.........

你现在看到了吗? 你不能没有你的基础暴露的构造函数。然后,base 将在您设置虚拟成员之前执行其构造函数。

【讨论】:

加粗在源代码中不起作用 - 您只需要使用内联 cmets。 我知道。这只是为了让某人关注特定部分 - 看起来像你一样:o) @T.S.谢谢,我现在明白了。您的回答很有帮助,但不是完整的答案。我将为这个问题添加另一个答案。再次感谢您的帮助。 @EdwardNedHarvey 只需在 Google 上搜索“在构造函数中调用虚拟成员”之类的内容。为什么不这样做有不同的例子。要点 - 这不是犯罪,但如果你使用这种技术,你可能会得到不正确的结果。事实上,在某些设计中你可以接受,因为你的对象继承的使用方式。我记得有一个应用程序充满了它,但从来没有任何问题,因为它是按设计完成的。 @T.S.是的,在发布这个问题之前,我已经在谷歌上搜索过,并阅读了有关该主题的 msdn。 msdn.microsoft.com/en-us/library/ms182331.aspx 但答案从来都不清楚。根据设计,我在问题中编写的代码似乎可以安全地使用,但我错了。现在我有了真正的答案为什么。再次感谢您的帮助。【参考方案3】:

我想在构造函数中添加调用和覆盖方法会使您的程序处于不一致的状态。如果你的方法抛出异常会发生什么?那么你的对象将永远不会被构造。在构造函数中捕获这些异常并不是一个好习惯。

ctor()

    method(); //throws an exception

您可以从 Windows 窗体中学到的一个教训是,设计器有一个从构造函数调用的 InitializeComponents。

public MyView: System.Windows.Forms.Form

   public MyView()
   
      InitializeComponent();
   

InitializeComponent 由设计器生成。不要修改它 因为更改设计器属性时更改将丢失。 InitializeComponent 的目的仅仅是为了让设计者把所有 它的代码设置所有属性,并在某个地方读取 绘制设计器表面,以呈现相关的 组件设置

如果 InitializeComponent 是一个覆盖方法怎么办?然后您可以对其进行修改,最后,如果您的更改错误并破坏了基类的逻辑,整个表单可能会处于不一致的状态

【讨论】:

以上是关于为啥不在构造函数中调用可覆盖的方法?的主要内容,如果未能解决你的问题,请参考以下文章

请教一下C#中父类静态构造函数在子类中为啥不会和子类的静态构造函数一起执行

子类为啥要调用父类的构造函数

为啥不在构造函数中启动一个线程?如何终止?

如何覆盖从构造函数调用的基类方法

为啥从类构造函数调用的方法应该是最终的? [复制]

为啥在 Angular 2 的构造函数中编写初始化逻辑不是一个好习惯