我今天正在审查一些代码并遇到了一些代码(由此代码段准确描绘)...
public abstract class FlargBase{
public FlargBase(){
this.DoSomething();
}
public abstract void DoSomething();
}
public class PurpleFlarg: FlargBase{
public PurpleFlarg()
: base(){
}
public override void DoSomething(){
// Do something here;
}
}
编译器不提供任何错误或警告,但CodeAnalysis警告调用链包含对虚方法的调用,并可能产生意外结果。
我很好奇,因为正如我所看到的,可能会发生两件事。
此代码已在生产环境中使用了几个月。它显然工作正常,没有人注意到任何奇怪的行为。
我希望StackOverflow上令人难以置信的才能可以让我对这段代码的行为和后果有所了解。
答案 0 :(得分:12)
在第一个构造函数运行之前,C#对象已完全构造并初始化为零。基础构造函数将调用虚方法的派生实现。
执行此操作被认为是不好的样式,因为派生类的构造函数尚未被调用时,派生实现可能会表现得很奇怪。但这种行为本身就是明确的。如果在派生实现中没有做任何事情,需要构造函数中的代码已经运行,那么它将起作用。
您可以映像运行时首先调用最派生的构造函数。它的第一个动作是隐式调用基础构造函数。我不确定它是否实际上是这样实现的,但是由于某些.net语言允许您在派生构造函数的任意点调用基本构造函数,我希望C#只需调用基类构造函数作为派生的第一个动作构造
此行为与C ++处理它的方式非常不同。在C ++中,派生类一个接一个地构造,并且在派生类的构造函数启动之前,对象仍然是基类的类型,并且忽略派生类的覆盖。
答案 1 :(得分:2)
您的PurpleFlarg.DoSomething()
在PurpleFlarg()
构造函数体之前执行。
这可能会导致意外,因为一般的假设始终是构造函数是对对象进行操作的第一种方法。
以下是MSDN page,其中包含“错误”条件的示例。
答案 2 :(得分:2)
在C#中,覆盖方法总是解析为派生最多的实现。在C# spec here:
的10.11.3(构造函数执行)中给出了一个示例将变量初始值设定项转换为赋值语句,并且 这些赋值语句在调用之前执行 基类实例构造函数。这种排序确保了所有 实例字段之前由其变量初始化程序初始化 任何有权访问该实例的语句都会被执行。
给出示例
using System; class A { public A() { PrintFields(); } public virtual void PrintFields() {} } class B: A { int x = 1; int y; public B() { y = -1; } public override void PrintFields() { Console.WriteLine("x = {0}, y = {1}", x, y); } }
当新的B()用于创建B的实例时,输出如下 制作:
x = 1, y = 0
答案 3 :(得分:0)
如果类包含抽象方法(DoSomething),那么该类也必须是抽象的,并且无法实例化。
答案 4 :(得分:0)
嗯,这个模式对于现实中对象的可覆盖工厂非常有用,所以像下一个代码中的那个案例在我看来完全合法且写得很好。
abstract class MyBase
{
public object CustomObject { get; private set; }
public MyBase()
{
this.CustomObject = this.CreateCustomObject();
}
protected abstract object CreateCustomObject();
}
class MyBaseList : MyBase
{
protected override object CreateCustomObject()
{
return new List<int>();
}
}
class MyBaseDict : MyBase
{
protected override object CreateCustomObject()
{
return new Dictionary<int, int>();
}
}