在构造函数中调用虚拟成员

我从ReSharper收到关于从我的对象构造函数调用虚拟成员的警告。

为什么这是不该做的事情?


当用C#编写的对象被构​​造时,会发生什么是初始化器从大多数派生类到基类的顺序运行,然后构造函数按从基类到最派生类的顺序运行(请参阅Eric Lippert的博客以了解详细信息至于为什么这是)。

在.NET中,对象也不会在构造时改变类型,但是从最为派生的类型开始,方法表用于最派生的类型。 这意味着虚拟方法调用总是运行在最派生的类型上。

当你将这两个事实结合起来的时候,你会遇到这样的问题:如果你在构造函数中进行虚拟方法调用,并且它不是继承层次结构中派生最多的类型,那么它将在其构造函数未被构造的类上被调用运行,因此可能不处于适当的状态来调用该方法。

如果将类标记为密封,以确保它是继承层次结构中派生最多的类型,则此问题当然会得到缓解 - 在这种情况下,调用虚拟方法是非常安全的。


为了回答你的问题,考虑这个问题:当Child对象被实例化时,下面的代码会打印出什么?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

答案是,实际上会抛出NullReferenceException ,因为foo为空。 一个对象的基础构造函数在它自己的构造函数之前被调用 。 通过在对象的构造函数中进行virtual调用,您可以引入继承对象在完全初始化之前执行代码的可能性。


C#的规则与Java和C ++的规则非常不同。

当您在C#中某个对象的构造函数中时,该对象以完全派生类型的形式存在于完全初始化(不是“构造”)窗体中。

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

这意味着,如果您从A的构造函数调用虚函数,它将解析为B中的任何覆盖(如果提供了覆盖)。

即使你这样故意设置A和B,充分理解系统的行为,你以后可能会感到震惊。 假设你在B的构造函数中调用了虚函数,“知道”它们将由B或A酌情处理。 然后时间流逝,而其他人决定他们需要定义C,并覆盖那里的一些虚拟功能。 突然之间B的构造函数最终在C中调用代码,这可能导致相当令人惊讶的行为。

无论如何,避免构造函数中的虚函数可能是一个好主意,因为C#,C ++和Java之间的规则是如此不同。 你的程序员可能不知道该期待什么!

链接地址: http://www.djcxy.com/p/14149.html

上一篇: Virtual member call in a constructor

下一篇: Performance differences between debug and release builds