我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
当前回答
我发现的另一件有趣的事情是,通过执行下面这样的操作可以“满足”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";
}
}
其他回答
因为在构造函数完成执行之前,对象不会完全实例化。虚拟函数引用的任何成员都不能初始化。在C++中,当您处于构造函数中时,这仅指所处构造函数的静态类型,而不是所创建对象的实际动态类型。这意味着虚拟函数调用甚至可能不会到达您期望的位置。
为了回答您的问题,请考虑以下问题:当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#编写的对象被构造时,会发生的情况是初始化器按照从最派生类到基类的顺序运行,然后构造函数按照从基类到最派生类的顺序运行(有关为什么这样做的详细信息,请参阅Eric Lippert的博客)。
此外,在.NET对象中,对象在构造时不会改变类型,而是从最派生的类型开始,方法表是最派生的。这意味着虚拟方法调用总是在最派生的类型上运行。
当你结合这两个事实时,你会遇到这样的问题:如果你在构造函数中调用了一个虚拟方法,并且它不是继承层次结构中最派生的类型,那么它将在一个尚未运行构造函数的类上被调用,因此可能不处于调用该方法的合适状态。
当然,如果您将类标记为密封的,以确保它是继承层次结构中最派生的类型,那么这个问题就会得到缓解——在这种情况下,调用虚拟方法是完全安全的。
您的构造函数(稍后,在软件的扩展中)可以从重写虚拟方法的子类的构造函数调用。现在不是子类的函数实现,而是基类的实现将被调用。所以在这里调用虚拟函数是没有意义的。
然而,如果您的设计满足Liskov替换原则,则不会造成任何伤害。也许这就是为什么它被容忍的原因——一个警告,而不是错误。
小心盲目地听从Resharper的建议,让课堂变得封闭!如果它是EF Code First中的模型,它将删除虚拟关键字,这将禁用其关系的延迟加载。
public **virtual** User User{ get; set; }