编译器琐事:这段代码的结果是什么

我今天正在审查一些代码并遇到了一些代码(由此代码段准确描绘)……

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警告调用链包含对虚方法的调用,并可能产生意外结果。

我很好奇,因为正如我所看到的,有两件事情可能发生。

  1. 创建基类的实例将调用没有定义实现的方法。 我希望编译器出错,或者由于缺少实现而导致运行时抛出exception。 我假设编译器提供了{}的实现 我错误输入了原始代码; 它确实在类中包含了abstract关键字。
  2. 创建派生类的实例将导致对尚未实际构造的类的方法进行调用。 我原以为这会抛出exception。

此代码已在生产环境中使用了几个月。 它显然工作正常,没有人注意到任何奇怪的行为。

我希望StackOverflow上的不可思议的人才可以让我对这段代码的行为和后果有所了解。

在第一个构造函数运行之前,AC#对象已完全构造并初始化为零。 基础构造函数将调用虚方法的派生实现。

这样做被认为是不好的风格,因为派生类的构造函数尚未被调用时,派生的实现可能会表现得很奇怪。 但这种行为本身就是明确的。 如果在派生实现中不执行任何操作,需要构造函数中的代码已经运行,那么它将起作用。

您可以映像运行时首先调用最派生的构造函数。 它的第一个动作是隐式调用基础构造函数。 我不确定它是否实际上是这样实现的,但是由于某些.net语言允许您在派生构造函数的任意点调用基本构造函数,我希望C#只是调用基类构造函数作为派生的第一个动作构造函数。


此行为与C ++处理它的方式非常不同。 在C ++中,派生类一个接一个地构造,并且在派生类的构造函数启动之前,对象仍然具有基类的类型,并且忽略派生类的覆盖。

您的PurpleFlarg.DoSomething()PurpleFlarg()构造函数体之前执行。

这可能会导致意外,因为一般的假设总是构造函数是对对象进行操作的第一种方法。

这是MSDN页面,其中包含“错误”条件的示例。

在C#中,覆盖方法始终解析为派生最多的实现。 这里给出了C#规范 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 

如果类包含抽象方法(DoSomething),那么该类也必须是抽象的,并且不能实例化。

嗯,这个模式对于现实中对象的可覆盖工厂非常有用,所以像下一个代码中的那个案例在我看来完全合法且写得很好。

 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(); } } class MyBaseDict : MyBase { protected override object CreateCustomObject() { return new Dictionary(); } }