Why do not call overridable methods in constructors?

时光总嘲笑我的痴心妄想 提交于 2019-12-04 00:34:34

问题


This is an oversimplified example, but I have some real-life code that conceptually does the same thing (trying to validate values "set" accessor methods of derivative classes), and the Analyzer gives me "Do not call overridable methods in constructors." I'm trying to figure out if I should change my code, or ignore the warning. I can't think of any reason I should heed the warning.

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();
        }
    }
}

回答1:


As T.S. said (thank you T.S.), the base constructor will always be called, when instantiating the derived type. If you do as I did in the question, where the derivative constructor does not explicitly specify which base constructor to use, then the parameterless base constructor is used.

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

And

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

So the complete answer to this question is: The abstract or virtual methods declared in the base class are only overridable in the base class, if you sealed the derivative class. So to make clean code, you don't run this line in the base constructor:

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

You instead, should "sealed" the derivative class (or method). As follows:

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();
        }
    }
}

Alternatively, you don't need to seal the entire derivative class, if you just seal the overridable method (making it no longer overridable by any further derivative classes)

public class HttpUrl : SimpleUrl
{
    // ...
    public override sealed string Url
    // ...



回答2:


The answer really is, this can lead to unexpected behavior.

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

Something you missed in your code:

public class HttpUrl : SimpleUrl
{
    public HttpUrl()**:base()**
    { }
    public HttpUrl(string Url)**:base(Url)**
    {
        this.Url = Url;
    }
.........
}

Do you see now? You can't NOT have constructor your base exposes. And then, base will execute its constructor before you set your virtual member.




回答3:


I want to add that calling and override method in the constructor can leave your program in an inconsistent state. What happen if your method throws an exception? Then your object will never be constructed. And is not a good practice to catch those exceptions inside the constructor.

ctor()
{
    method(); //throws an exception
}

A lesson you can learn from Windows Form, is that the designer has an InitializeComponents that is called from the constructor.

public MyView: System.Windows.Forms.Form
{
   public MyView()
   {
      InitializeComponent();
   }
}

InitializeComponent is generated by the designer. Do not modify it because changes will be lost when the designer properties are changed. InitializeComponent's purpose is solely for the designer to put all its code to set all properties, and somewhere for it to read when drawing the designer surface, in order to render the relevant component settings

What if InitializeComponent was an override method? Then you could modify it and at the end, the entire form could be in an inconsistent state if your changes are wrong and break the logic on the base class



来源:https://stackoverflow.com/questions/20864675/why-do-not-call-overridable-methods-in-constructors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!