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

为什么这是不该做的?


当前回答

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

其他回答

为了回答您的问题,请考虑以下问题:当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为空。对象的基构造函数在其自身构造函数之前调用。通过在对象的构造函数中进行虚拟调用,您引入了继承对象在完全初始化之前执行代码的可能性。

这个问题的一个重要方面(其他答案尚未解决)是,如果派生类希望基类从其构造函数中调用虚拟成员,则基类是安全的。在这种情况下,派生类的设计者负责确保在构造完成之前运行的任何方法都将在这种情况下尽可能合理地运行。例如,在C++/CLI中,构造函数被包装在代码中,如果构造失败,这些代码将对部分构造的对象调用Dispose。在这种情况下,调用Dispose通常是防止资源泄漏所必需的,但Dispose方法必须做好准备,以防运行它们的对象可能尚未完全构造。

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

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

对于你为什么不想这样做,上面有很好的答案。这里有一个反例,也许你想这样做(由Sandi Metz翻译成C#,来自Ruby中的实用面向对象设计,第126页)。

注意,GetDependency()没有触及任何实例变量。如果静态方法可以是虚拟的,那么它将是静态的。

(公平地说,可能有更聪明的方法通过依赖注入容器或对象初始化器来实现这一点…)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

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