Почему не вызывать переопределяемые методы в конструкторах?

Это упрощенный пример, но у меня есть реальный код, который концептуально делает то же самое (пытается проверить значения "установить" методы доступа производных классов), и анализатор дает мне "не вызывать переопределяемые методы в конструкторах."Я пытаюсь понять, должен ли я изменить свой код или игнорировать предупреждение. Не знаю, почему я должен прислушаться к предупреждению.

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

3 ответов


как сказал 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
    // ...

ответ действительно есть, это может привести к неожиданному поведению.

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 выполнит свой конструктор, прежде чем вы установите свой виртуальный член.


Я хочу добавить, что вызов и переопределение метода в конструкторе могут оставить вашу программу в несогласованном состоянии. Что произойдет, если ваш метод выдает исключение? Тогда ваш объект никогда не будет построен. И это не хорошая практика, чтобы поймать эти исключения в конструкторе.

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

урок, который вы можете узнать из Windows Form, заключается в том, что конструктор имеет InitializeComponents, который вызывается из конструктора.

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

InitializeComponent создается конструктором. Не изменяйте его потому что изменения будут потеряны при изменении свойств конструктора. Цель InitializeComponent является исключительно для дизайнера, чтобы положить все его код для установки всех свойств и где-то для его чтения, когда рисовать поверхность конструктора, для того чтобы представить уместное настройки компонентов

что, если InitializeComponent был методом переопределения? Затем вы можете изменить его и в конце, вся форма может быть в непоследовательном состоянии, если ваши изменения неверны и нарушают логику в базовом классе