覆盖并在C#中的Base类构造函数中调用相同的方法

Lea*_*ner 2 .net c# asp.net c#-4.0

我的烦恼是:在下面的代码提交,它应该显示A然后B.但它显示B然后B.为什么会这样?

我的感觉是,A在创建对象时首先执行get的构造函数B.在那种情况下,方法B不会正确吗?所以应该A.Display()而且应该结果A.此外,然后a.Display()应该返回B因为我们有覆盖.

因此,我希望AB.因为它没有超载而是压倒一切.我知道这些东西的定义,我希望了解这种现象的原因以及它是如何工作的内部,因为我不相信有BB,但AB.


class A
{
    public A()
    {
        this.Display();
    }
    public virtual void Display()
    {
        Console.WriteLine("A");
    }
}

class B :A
{
    public override void Display()
    {
        Console.WriteLine("B");
    }
}

class C
{
    static void Main()
    {
        A a = new B();
        a.Display();
        Console.WriteLine();
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出

1)在Display派生类中重写方法时产生以下内容:

A a = new A(); // ---> AA
B a = new B(); // ---> BB // I expect AB.
A a = new B(); // ---> BB // I expect AB.
Run Code Online (Sandbox Code Playgroud)

2)在派生类的方法中使用NEW关键字Display产生以下结果:

B a = new B(); // ---> AB // I Expect AA here.
A a = new B(); // ---> AA
A a = new A(); // ---> AA
Run Code Online (Sandbox Code Playgroud)

3)更有趣的发现是:

当我base.Display()在派生构造函数中使用派生类中的基本方法的覆盖时,它给了我BAB

至少在这方面我没有看到任何逻辑.因为,它应该给BBB

Eri*_*ert 9

我的感觉是,A的构造函数在创建B的对象时首先被执行.

正确.

在那种情况下,B中的方法不会被击中吗?

这是不正确的.

在C++中的类似代码中,您将是正确的.在C++中,有一个规则是在构造对象时构建虚函数调度表.也就是说,当输入"A"构造函数时,vtable用"A"中的方法填充.当控制进入"B"ctor时,然后使用B的方法填充vtable.

在C#中不是这种情况.在C#中,vtable 在对象从内存分配器出来之前填充,在执行ctor之前,并且在此之后它不会改变.该方法的vtable槽始终包含派生最多的方法.

因此,像在这里一样在ctor中调用虚拟方法是一个非常糟糕的主意.可以调用虚方法,其中实现在ctor尚未运行的类上!因此,它可能取决于尚未初始化的州.

请注意,字段初始值设定项在所有ctor体之前运行,幸运的是,对更多派生类的覆盖将始终在覆盖类的字段初始值设定项之后运行.

故事的寓意是:根本不这样做.不要在ctor中调用虚方法.在C++中,您可能会获得与预期不同的方法,在C#中,您可能会获得一个使用未初始化状态的方法.避免,避免,避免.

为什么我们不应该在ctor中调用虚方法?是因为我们总是只在vtable中获得(最新派生的)结果吗?

是.让我用一个例子来说明:

class Bravo
{
    public virtual void M() 
    {
        Console.WriteLine("Bravo!");
    }
    public Bravo()
    {
        M(); // Dangerous!
    }
}
class Delta : Bravo:
{
    DateTime creation;
    public override void M() 
    {
        Console.WriteLine(creation);
    }
    public Delta() 
    {
        creation = DateTime.Now;
    }
}
Run Code Online (Sandbox Code Playgroud)

好的,所以这个程序的预期行为是,当M调用any时Delta,它将打印出实例创建的时间.但事件的顺序new Delta()是:

  • Bravo ctor跑
  • Bravo ctor打电话 this.M
  • M是虚拟的,this属于运行时类型,Delta因此Delta.M运行
  • Delta.M打印出未初始化的字段,该字段设置为默认时间,而不是当前时间.
  • M 回报
  • Bravo ctor回来了
  • Delta ctor设置了这个领域

现在,当我说重写方法可能依赖于尚未初始化的状态时,你看到了我的意思吗?在任何其他用法中M,这都可以,因为Deltactor已经完成了.但是这个MDeltactor开始之前被召唤了!