超类构造函数中的虚拟化
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 的代码分析也会解决这个特定问题。
【讨论】:
以上是关于超类构造函数中的虚拟化的主要内容,如果未能解决你的问题,请参考以下文章