2011-08-12 60 views
4

我正在检讨一些代码,今天遇到了一些代码(由这个片段精确地画出)传来的后果......编译花絮:这是什么代码

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. 创建基类的一个实例将为方法的调用没有实现定义。我希望编译器出错,或者运行时由于缺少实现而引发异常。我假设编译器提供了{}的实现我错误地输入了原始代码;它确实包含了类中的抽象关键字。
  2. 创建派生类的实例将导致对尚未实际构建的类的方法的调用。我会预料到这会抛出一个异常。

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

我希望StackOverflow中令人难以置信的才能可以让我对这段代码的行为和后果有所了解。

+0

我得到一个错误:'“ConsoleApplication1.FlargBase.DoSomething()”是抽象的,但它包含在非抽象类“ConsoleApplication1.FlargBase''。此外,在创建派生类的实例时逐句通过代码显示它在派生类中调用DoSomething()。 – Stijn

+0

这与脆性基类问题密切相关。见http://blogs.msdn.com/b/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes.aspx – Odrade

回答

12

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

这样做被认为是不好的风格,因为派生类的构造函数尚未被调用时,派生实现可能会表现异常。但是,行为本身是明确的。如果你在派生实现中没有做任何事情,它需要来自构造函数的代码已经运行,它将会工作。

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


这种行为与C++如何处理它有很大不同。在C++中,派生类依次构造,并且在派生类的构造函数启动之前,对象仍具有基类的类型,并忽略派生类的替代。

+0

你也许应该改变你的第一句话的一些术语,因为现在它是同义的错误。该对象在执行构造函数之前存在,但在构造之前不会构造该对象。 –

0

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

2

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

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

以下是MSDN page,其中有一个“错误”条件示例。

2

在C#中,重写方法总是解析为最派生的实现。一个例子10.11.3(构造函数执行)给出C# spec here的:

Variable initializers are transformed into assignment statements, and these assignment statements are executed before the invocation of the base class instance constructor. This ordering ensures that all instance fields are initialized by their variable initializers before any statements that have access to that instance are executed.

Given the example

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

when new B() is used to create an instance of B, the following output is produced:

x = 1, y = 0 
+0

这是如何解决实际问题的? –

+0

@亨克,这意味着代码将永远不会做任何未定义的事情(如抛出一个AV),但它可能不会做你期望的事情。 – MSN

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