我认为根据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 ++中有效,因为自IMO以来,对PrintRandom的调用没有路由到派生类,C ++中的顺序类似于:
1
。调用基类构造函数
2。更新V - 此课程的表格
3。调用构造函数代码
我的问题是,首先我是否正确,根据OOP原则,虚拟化不应该/不能在超类构造函数中工作,其次,如果我是对的,那么为什么所有.NET语言的行为都不同(我用C#,VB.NET和MC ++测试过它)
答案 0 :(得分:4)
在本机C ++中,程序按预期工作:您可以在基类构造函数中调用虚函数的基类版本。在构造函数调用时,只存在基类及其虚函数,因此您将获得当时定义的虚函数的最低级版本。这并不意味着不能使用虚拟化,你只是不会在基类的构造函数中获得虚方法的子类版本(这就是为什么不推荐它)。
显然,正如您所看到的,托管代码的工作方式不同,因为(iirc)整个对象是在调用构造函数之前构建的,因此您可以在子类构造函数之前获得子类虚函数。这是语言行为之间的文档差异,但在.NET语言中应该是一致的(因为它们都编译为相同的IL)。
答案 1 :(得分:3)
在我看来,这不是OO原则的问题 - 它取决于它所处理这个特定难题的平台。因此,不鼓励从构造函数中调用虚方法,但是 - 如果您要这样做,则需要明确记录非常您将要调用它,以便任何覆盖它的类知道会发生什么。
Java采用与.NET 相同的方法,除了,在C#中,任何实例变量初始化程序在进行基本构造函数调用之前执行。这意味着在您的特定示例中,您可以通过在声明点初始化random
来修复代码。在Java中无济于事。
至于为什么MC ++ 不以这种方式工作,我不知道 - 我建议你比较生成的IL。我的猜测是它显式地进行了非虚方法调用。
编辑:我怀疑我误解了这个问题 - MC ++的工作方式是什么?如果它以C#的工作方式工作,这对IMO来说是一件好事,可以在.NET平台上提供一致的视图。
答案 2 :(得分:0)
我建议在代码上使用FxCop。我和许多人一起工作,他们忽略了这个工具提出的项目是无关紧要的,但是,如果你的代码包含很多小问题(例如你的代码),那么被一个或多个被咬的可能性要高得多。 / p>
ReSharper的代码分析也会解决这个问题。