超类构造函数中的虚拟化

Posted

技术标签:

【中文标题】超类构造函数中的虚拟化【英文标题】:Virtualization in Super Class Constructor 【发布时间】:2008-11-21 06:39:31 【问题描述】:

根据 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 的对象时,它会调用基类构造函数,并且我们在基类构造函数中有一个虚拟方法调用,该调用又调用 Derived 类的方法,因此,由于未初始化 randomValue,它会崩溃到那个时候。

类似的代码在 C++ 中有效,因为自 IMO 以来对 PrintRandom 的调用未路由到派生类,C++ 中的顺序类似于:

1.调用基类构造函数 2。更新 V - 此类的表 3。调用构造函数代码

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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

【参考方案2】:

在我看来,这与 OO 原则无关 - 取决于相关平台如何处理这个特殊难题。正是出于这个原因,不鼓励从构造函数调用虚方法,但是 - 如果你要这样做,你需要明确地记录 very 你将要调用它,以便任何覆盖它的类知道会发生什么。

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

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

编辑:我怀疑我误读了这个问题 - MC++ 以哪种方式工作?如果它以 C# 的方式工作,那是 IMO 的一件好事,它提供了跨 .NET 平台的一致视图。

【讨论】:

【参考方案3】:

我建议在您的代码中使用 FxCop。我曾与许多人一起工作,他们认为此工具提出的问题无关紧要,但是,如果您的代码包含许多小问题(例如您的),那么被一个或多个问题咬伤的机会要高得多。

ReSharper 的代码分析也会解决这个特定问题。

【讨论】:

以上是关于超类构造函数中的虚拟化的主要内容,如果未能解决你的问题,请参考以下文章

Java 中的继承——创建子类的对象也会调用超类的构造函数。为啥?

将参数传递给超类构造函数

从构造函数的主体调用超类构造函数

Matlab调用超类抽象构造函数语法看起来很奇怪

构造函数重载 - Java 中的最佳实践 [关闭]

为啥总是调用超类构造函数[重复]