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

为什么这是不该做的?


当前回答

是的,在构造函数中调用虚拟方法通常是不好的。

此时,对象可能还没有完全构造,方法所期望的不变量可能还不成立。

其他回答

为了回答您的问题,请考虑以下问题:当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!?!
    }
}

答案是,实际上会引发NullReferenceException,因为foo为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。

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

下面的父类试图将值设置为其构造函数上的虚拟成员。这将触发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"
    }
} 

是的,在构造函数中调用虚拟方法通常是不好的。

此时,对象可能还没有完全构造,方法所期望的不变量可能还不成立。

在这个特定的例子中,C++和C#之间有区别。在C++中,对象未初始化,因此在构造函数中调用病毒函数是不安全的。在C#中,当创建类对象时,其所有成员都是零初始化的。可以在构造函数中调用虚拟函数,但如果您将访问仍然为零的成员。如果您不需要访问成员,那么在C#中调用虚拟函数是非常安全的。

我发现的另一件有趣的事情是,通过执行下面这样的操作可以“满足”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";
   }
}