C#继承和虚函数混淆

时间:2012-09-28 17:09:26

标签: c# inheritance virtual

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

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

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

5 个答案:

答案 0 :(得分:11)

  

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

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

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

编辑:请注意,如果您未在构造函数声明中使用{chain}来提供另一个构造函数调用来使用: this(...): base(...),则它等同于使用: base()。所以B的构造函数相当于:

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

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

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

如果以这种方式定义了A类构造函数,则不会得到双输出:

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

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

两次打印“打印在B”的原因是在B类中重写了Print(),因此B.Print()是两个构造函数调用的。

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

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

希望这有帮助。

答案 4 :(得分:1)

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