我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
我从ReSharper收到一条关于从对象构造函数调用虚拟成员的警告。
为什么这是不该做的?
当前回答
因为在构造函数完成执行之前,对象不会完全实例化。虚拟函数引用的任何成员都不能初始化。在C++中,当您处于构造函数中时,这仅指所处构造函数的静态类型,而不是所创建对象的实际动态类型。这意味着虚拟函数调用甚至可能不会到达您期望的位置。
其他回答
当用C#编写的对象被构造时,会发生的情况是初始化器按照从最派生类到基类的顺序运行,然后构造函数按照从基类到最派生类的顺序运行(有关为什么这样做的详细信息,请参阅Eric Lippert的博客)。
此外,在.NET对象中,对象在构造时不会改变类型,而是从最派生的类型开始,方法表是最派生的。这意味着虚拟方法调用总是在最派生的类型上运行。
当你结合这两个事实时,你会遇到这样的问题:如果你在构造函数中调用了一个虚拟方法,并且它不是继承层次结构中最派生的类型,那么它将在一个尚未运行构造函数的类上被调用,因此可能不处于调用该方法的合适状态。
当然,如果您将类标记为密封的,以确保它是继承层次结构中最派生的类型,那么这个问题就会得到缓解——在这种情况下,调用虚拟方法是完全安全的。
重要的一点是,解决这个问题的正确方法是什么?
正如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#中,基类构造函数在派生类构造函数之前运行,因此派生类可能在可能重写的虚拟成员中使用的任何实例字段都尚未初始化。
请注意,这只是一个警告,让你注意并确保一切正常。这个场景有实际的用例,您只需要记录虚拟成员的行为,它不能使用下面的派生类中声明的任何实例字段,构造函数调用它。
对于你为什么不想这样做,上面有很好的答案。这里有一个反例,也许你想这样做(由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++和C#之间有区别。在C++中,对象未初始化,因此在构造函数中调用病毒函数是不安全的。在C#中,当创建类对象时,其所有成员都是零初始化的。可以在构造函数中调用虚拟函数,但如果您将访问仍然为零的成员。如果您不需要访问成员,那么在C#中调用虚拟函数是非常安全的。