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

为什么这是不该做的?


当前回答

只是为了补充我的想法。如果在定义私有字段时总是初始化它,那么应该避免这个问题。至少以下代码就像一个符咒:

class Parent
{
    public Parent()
    {
        DoSomething();
    }
    protected virtual void DoSomething()
    {
    }
}

class Child : Parent
{
    private string foo = "HELLO";
    public Child() { /*Originally foo initialized here. Removed.*/ }
    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

其他回答

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

重要的一点是,解决这个问题的正确方法是什么?

正如Greg所解释的,这里的根本问题是基类构造函数会在构造派生类之前调用虚拟成员。

以下代码摘自MSDN的构造函数设计指南,演示了这个问题。

public class BadBaseClass
{
    protected string state;

    public BadBaseClass()
    {
        this.state = "BadBaseClass";
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBad : BadBaseClass
{
    public DerivedFromBad()
    {
        this.state = "DerivedFromBad";
    }

    public override void DisplayState()
    {   
        Console.WriteLine(this.state);
    }
}

创建DerivedFromBad的新实例时,基类构造函数调用DisplayState并显示BadBaseClass,因为派生构造函数尚未更新该字段。

public class Tester
{
    public static void Main()
    {
        var bad = new DerivedFromBad();
    }
}

一个改进的实现从基类构造函数中删除了虚拟方法,并使用了Initialize方法。创建DerivedFromBetter的新实例将显示预期的“DerivedFromBetter”

public class BetterBaseClass
{
    protected string state;

    public BetterBaseClass()
    {
        this.state = "BetterBaseClass";
        this.Initialize();
    }

    public void Initialize()
    {
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBetter : BetterBaseClass
{
    public DerivedFromBetter()
    {
        this.state = "DerivedFromBetter";
    }

    public override void DisplayState()
    {
        Console.WriteLine(this.state);
    }
}

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

我认为,如果您想让子类能够设置或重写父构造函数将立即使用的属性,则忽略警告可能是合法的:

internal class Parent
{
    public Parent()
    {
        Console.WriteLine("Parent ctor");
        Console.WriteLine(Something);
    }

    protected virtual string Something { get; } = "Parent";
}

internal class Child : Parent
{
    public Child()
    {
        Console.WriteLine("Child ctor");
        Console.WriteLine(Something);
    }

    protected override string Something { get; } = "Child";
}

这里的风险是子类从其构造函数设置属性,在这种情况下,值的更改将在调用基类构造函数之后发生。

我的用例是,我希望子类提供一个特定的值或一个实用程序类(如转换器),而不需要在基础上调用初始化方法。

在实例化子类时,上面的输出是:

Parent ctor
Child
Child ctor
Child

小心盲目地听从Resharper的建议,让课堂变得封闭!如果它是EF Code First中的模型,它将删除虚拟关键字,这将禁用其关系的延迟加载。

    public **virtual** User User{ get; set; }