超类构造函数中的虚拟化

我认为根据OOP的设计,虚拟化在超类构造函数中不起作用。 例如,请考虑以下C#代码。

using System; namespace Problem { public class BaseClass { public BaseClass() { Console.WriteLine("Hello, World!"); this.PrintRandom(); } public virtual void PrintRandom() { Console.WriteLine("0"); } } public class Descendent : BaseClass { private Random randomValue; public Descendent() { Console.WriteLine("Bonjour, Monde!"); randomValue = new Random(); } public override void PrintRandom() { Console.WriteLine(randomValue.NextDouble().ToString()); } public static void Main() { Descendent obj = new Descendent(); obj.PrintRandom(); Console.ReadLine(); } } } 

这段代码中断了,因为当Descendent的对象被创建时,它调用基类构造函数,并且我们在Base Class构造函数中调用了一个虚方法,该构造函数又调用了Derived类的方法,因此它崩溃了,因为randomValue在那个时候没有初始化。

类似的代码在C ++中工作,因为对PrintRandom的调用没有路由到派生类,因为IMO,C ++中的顺序是这样的:

1.调用基类构造函数
2.更新V – 此课程的表格
3.调用构造函数代码

我的问题是,首先我是否正确,根据OOP原则,虚拟化不应该/不能在超类构造函数中工作,其次,如果我是对的,那么为什么行为在所有.NET语言中都不同(我有用C#,VB.NET和MC ++测试它

在本机C ++中,程序按预期工作:您可以在基类构造函数中调用虚函数的基类版本。 在构造函数调用时,只存在基类及其虚函数,因此您将获得当时定义的虚函数的最低级版本。 这并不意味着不能使用虚拟化,你只是不会在基类的构造函数中获得虚方法的子类版本(这就是为什么不推荐它)。

显然,正如您所看到的,托管代码的工作方式不同,因为(iirc)整个对象是在调用构造函数之前构建的,因此您可以在子类构造函数之前获得子类虚函数。 这是语言行为之间的文档差异,但应该在.NET语言中保持一致(因为它们都编译为相同的IL)。

在我看来,这不是OO原则的问题 – 它取决于它所处理这个特定难题的平台。 因此,不鼓励从构造函数调用虚方法,但是 – 如果你要这样做,你需要非常明确地记录你将要调用它,以便任何覆盖它的类都知道会发生什么。

Java采用与.NET相同的方法, 除了在C#中,任何实例变量初始化程序在进行基础构造函数调用之前执行。 这意味着在您的特定示例中,您可以通过在声明点初始化random来修复代码。 在Java中无济于事。

至于为什么MC ++ 不能以这种方式工作,我不知道 – 我建议你比较生成的IL。 我的猜测是它明确地进行了非虚方法调用。

编辑:我怀疑我误解了这个问题 – MC ++的工作方式是什么? 如果它以C#的工作方式工作,那么IMO就是一件好事,可以在.NET平台上提供一致的视图。

我建议在你的代码上使用FxCop。 我和许多人一起工作,他们忽略了这个工具提出的项目是无关紧要的,但是,如果你的代码包含很多小问题(例如你的代码),那么被一个或多个被咬的可能性要高得多。

ReSharper的代码分析也将解决这一特定问题。