【问题标题】:C# inheritance and virtual functions confusionC#继承与虚函数混淆
【发布时间】:2012-09-20 14:23:51
【问题描述】:

今天我想出了一个有趣的问题。我注意到以下代码:

class A
{
    public A()
    {
        Print();
    }
    public virtual void Print()
    {
        Console.WriteLine("Print in A");
    }
}

class B : A
{
    public B()
    {
        Print();
    }

    public override void Print()
    {
        Console.WriteLine("Print in B");
    }
}

class Program
{
    static void Main(string[] args)
    {
        A a = new B();
    }
}

打印

Print in B
Print in B

我想知道为什么它会打印两次“Print in B”。

【问题讨论】:

  • 你不会每天都得到@JonSkeet 的答案!向我的朋友学习 Stack Overflow 的尤达。
  • 你得到一个编译器警告,告诉你这个类应该被密封?
  • @JohanLarsson 不是。没有警告,编译流畅。 VS 2010, 4.0 框架

标签: c# inheritance virtual


【解决方案1】:

我想知道为什么它会打印两次“Print in B”。

您在同一个对象上调用了两次虚拟方法。即使在A 的构造函数期间,该对象也是B 的实例,因此将调用被覆盖的方法。 (我相信在 C++ 中,就多态性而言,对象仅在基类构造函数执行之后“成为”子类的一个实例。)

请注意,这意味着从构造函数调用的重写方法将在派生类的构造函数体有机会执行之前执行。这是危险的。正是出于这个原因,您几乎不应该从构造函数中调用抽象或虚拟方法。

编辑:请注意,当您不提供对“链”的另一个构造函数调用以在构造函数声明中使用: this(...): base(...) 时,它等效于使用: base()。所以B的构造函数相当于:

public B() : base()
{
    Print();
}

有关构造函数链接的更多信息,请参阅my article on the topic

【讨论】:

  • 这不是隐式调用吗?我猜 MSIL 对于 public B() 和 public B() 是相等的:base()?
  • 我认为这里需要注意的是,在创建B 的新实例时会调用A 上的默认构造函数,即使没有像public B() : base() 那样显式调用它
  • @sybkar:我认为这是给定的,但我会明确表示。
  • 每个人的家中都应该有一个 Jon Skeet :-D
  • @NM:是的。不要在B 中调用Print()
【解决方案2】:

如果不调用基类构造函数,则会隐式调用基类的默认构造函数 (MSDN)。

如果您以这种方式定义 A 类构造函数,您将不会获得双重输出:

class A
{
    public A()
    {
        // does nothing
    }

    public A(object a)
    {
        Print();
    }
}

它两次打印“Print in B”的原因是 Print() 在 B 类中被覆盖,所以 B.Print() 是两个构造函数都调用的。

我认为您可以强制在 A 构造函数中调用 A.Print(),如下所示:

class A
{
    public A()
    {
        ((A)this).Print();
    }
}

希望这会有所帮助。

【讨论】:

  • 为什么说(A)this 会有所帮助?它仍然是B,而不是A
  • @Jeppe - 未验证。仅当 B 声明 [new void Print()] 时才有效,但不能这样做是 Print() 是虚拟的。
【解决方案3】:

因为你有一个 B 的实例,它覆盖了 Print,所以这个被覆盖的方法被调用了。此外,A 的 ctor 将运行,然后是 B 的,这就是它被打印两次的原因。

【讨论】:

    【解决方案4】:

    因为 B 覆盖了 Print 方法。你的变量a 的类型是B,而B 的Print 方法如下所示:

    public override void Print()
    {
        Console.WriteLine("Print in B");
    }
    

    【讨论】:

      【解决方案5】:

      与 C++ 的构造函数中的虚函数调用仅限于类本身的定义不同,重写在 C# 的构造函数中完全得到尊重。这种做法不受欢迎,并且有充分的理由 (link),但它仍然被允许:A 的构造函数调用 B 提供的覆盖,产生您看到的输出。这是被覆盖的虚函数的正常行为。

      【讨论】:

        猜你喜欢
        • 2011-12-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-09
        • 2017-03-18
        • 1970-01-01
        • 2010-10-11
        相关资源
        最近更新 更多