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

这是一个过于简单的例子,但是我有一些现实生活中的代码在概念上做同样的事情(试图validation派生类的“set”访问器方法的值),并且Analyzer给了我“不要在构造函数中调用可覆盖的方法”。 我试图找出是否应该更改我的代码,或忽略警告。 我想不出任何理由我应该留意这个警告。

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

正如TS所说(谢谢TS),在实例化派生类型时,将始终调用基础构造函数。 如果你像我在问题中所做的那样,衍生构造函数没有明确指定要使用哪个基础构造函数,那么使用无参数基础构造函数。

 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将在您设置虚拟成员之前执行其构造函数。

我想补充说,构造函数中的调用和覆盖方法可能会使程序处于不一致状态。 如果您的方法抛出exception会发生什么? 那么你的对象永远不会被构造出来。 并且在构造函数中捕获这些exception并不是一个好习惯。

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

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

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

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

如果InitializeComponent是覆盖方法怎么办? 然后你可以修改它,最后,如果你的更改错误,整个表单可能处于不一致的状态,并打破基类的逻辑