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

为什么这是不该做的?


当前回答

我只需要向基类添加一个Initialize()方法,然后从派生构造函数调用它。在执行所有构造函数之后,该方法将调用任何虚拟/抽象方法/财产:)

其他回答

因为在构造函数完成执行之前,对象不会完全实例化。虚拟函数引用的任何成员都不能初始化。在C++中,当您处于构造函数中时,这仅指所处构造函数的静态类型,而不是所创建对象的实际动态类型。这意味着虚拟函数调用甚至可能不会到达您期望的位置。

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之间的规则是如此不同。你的程序员可能不知道该怎么做!

我发现的另一件有趣的事情是,通过执行下面这样的操作可以“满足”ReSharper错误,这对我来说是愚蠢的。然而,正如许多前面提到的,在构造函数中调用虚拟财产/方法仍然不是一个好主意。

public class ConfigManager
{
   public virtual int MyPropOne { get; private set; }
   public virtual string MyPropTwo { get; private set; }

   public ConfigManager()
   {
    Setup();
   }

   private void Setup()
   {
    MyPropOne = 1;
    MyPropTwo = "test";
   }
}

我只需要向基类添加一个Initialize()方法,然后从派生构造函数调用它。在执行所有构造函数之后,该方法将调用任何虚拟/抽象方法/财产:)

该警告提醒您虚拟成员很可能在派生类上被重写。在这种情况下,父类对虚拟成员所做的任何操作都将通过重写子类来撤消或更改。看一下这个小例子

下面的父类试图将值设置为其构造函数上的虚拟成员。这将触发Re sharper警告,请参见代码:

public class Parent
{
    public virtual object Obj{get;set;}
    public Parent()
    {
        // Re-sharper warning: this is open to change from 
        // inheriting class overriding virtual member
        this.Obj = new Object();
    }
}

此处的子类重写父属性。如果此属性未标记为virtual,编译器将警告该属性隐藏父类上的属性,如果有意,建议您添加“new”关键字。

public class Child: Parent
{
    public Child():base()
    {
        this.Obj = "Something";
    }
    public override object Obj{get;set;}
}

最后是对使用的影响,下面示例的输出放弃了父类构造函数设置的初始值。这就是Re sharper试图警告您的,在父类构造函数上设置的值将被子类构造函数覆盖,子类构造函数在父类构造器之后立即调用。

public class Program
{
    public static void Main()
    {
        var child = new Child();
        // anything that is done on parent virtual member is destroyed
        Console.WriteLine(child.Obj);
        // Output: "Something"
    }
}