超类构造函数中的虚拟化

时间:2008-11-21 06:39:31

标签: c# .net c++ oop

我认为根据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 ++测试过它)

3 个答案:

答案 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的代码分析也会解决这个问题。