C#中的虚函数

4
public class Base1
{
    public virtual void f()
    {
        Console.WriteLine("Base1.f()");
    }
}

public class Derived1 : Base1
{
    // Hides Base1.f() because 'override' was not specified
    public new virtual void f()
    {
        Console.WriteLine("Derived1.f()");
    }
}

public class Derived2 : Derived1
{
    // Overrides Derived1.f()
    public override void f()
    {
        Console.WriteLine("Derived2.f()");

        // Call base method
        base.f();
    }
}

class Program
{
    static void Main(string[] args)
        Base1 ob1 = new Derived1();
        ob1.f();

        Base1 ob2 = new Derived2();
        ob2.f();

        Derived1 ob3 = new Derived2();
        ob3.f();

        Derived2 ob4 = new Derived2();
        ob4.f();
    }
}


// Calls Derived2.f() because Derived2 overrides Derived1().f()
        Derived1 ob3 = new Derived2();
        ob3.f();

预计会发生的是

在IT技术方面。
Base1 ob2 = new Derived2();
ob2.f();
  1. 为什么会调用基类函数而不是派生类函数,导致调用了derived2函数。
  2. .net是否使用虚函数表(vtable)。

3
请编辑您的问题以使其格式合适——我本可以这样做,但我无法确定您最终想要做什么。 - Jon Skeet
1
@Jon 请坚持参加婚礼,你肯定可以在营地找到它! - David Heffernan
1
@David:刚刚看完婚礼,现在回到帐篷里了。 - Jon Skeet
1
@Jon 我觉得这场盛事还在继续。我实际上横跨大西洋前往美国逃避它,但是在这里每个电视频道都在播放!美国人就是喜欢英国皇家婚礼,你无法想象。 - David Heffernan
1
@David:这是我一直在认真思考的问题。我可以理解为什么英国人可能会对此感兴趣,也可以理解为什么英裔美国人可能会关心它。但我根本无法理解为什么普通的美国人会关心英国皇家婚礼。我肯定不会...有一两分钟,我几乎担心这可能会让我成为一个坏人,但我已经克服了这个问题。 - Cody Gray
@Cody,其实我认为美国人喜欢皇室比英国人更容易理解。因为美国没有王室,所以对于他们来说这种神秘感很吸引人。而对于我们英国人来说,王室已经是家常便饭了。 - David Heffernan
3个回答

6

编译期间静态分析使用的方法插槽取决于变量(或表达式)的类型,而不是实际对象。变量ob2被标记为Base1类型,因此使用Base1方法插槽。接着根据该插槽上的类型(本质上是虚函数表),选择正确的覆盖。所以使用基础函数。

要使用derived2函数,变量(或表达式)必须被标记为Derived1或其子类。


3
这里的问题是您混合了一些东西。基本上,您已经完成了以下操作:
  1. 在基类中定义了一个虚拟方法 f
  2. 从该基类继承,并创建了一个新的虚拟方法 f
  3. 从第二个类继承,并重写了 f,这将覆盖第二个类中的方法,而不是基类中的方法。
因此,当您说:
Base1 b = new Derived2();
b.f();

在这种情况下,你总是会调用 f 的基本实现,因为 Derived2 中被覆盖的 f 是不同的 f 方法。名称相同,但仍然是不同的方法。

原因是编译器会看到你正在调用来自 Base1 类的 f,所以它会调用那个。

由于没有类覆盖 Base1.f,所以你调用的就是它。


回答评论中的问题,严格来说,该类将有两个虚方法,都命名为 f。

然而,其中一个被 Derived1 引入的新方法所遮蔽。

在类内部,可以选择调用哪个方法:

public void MethodInDerived1()
{
    f();                            // calls Derived1.f()
    base.f();                       // calls Base1.f()
}

然而,从外部来看,您需要通过投掷来“选择”。

换句话说:

Derived1 d = new Derived1();
d.f();                              // calls Derived1.f()
((Base1)d).f();                     // calls Base1.f()

您也可以使用反射观察方法。如果您在LINQPad中执行以下代码,您将看到有两个名为f的方法:
void Main()
{
    typeof(Derived1).GetMethods().Dump();
}

public class Base1
{
    public virtual void f()
    {
        Debug.WriteLine("Base1.f");
    }
}

public class Derived1 : Base1
{
    public virtual new void f()
    {
        Debug.WriteLine("Derived1.f");
    }
}

public class Derived2 : Derived1
{
    public override void f()
    {
        Debug.WriteLine("Derived2.f");
        base.f();
    }
}

这个脚本的输出结果如下(已截取部分信息,右侧还有更多信息):

LINQPad脚本输出


严格来说,它将有两个,但在词法上你只能引用其中一个,即新的一个,因为它们有相同的名称。不过,你可以使用反射找到它们。你也可以调用 base.f 来显式地选择基类方法。 - Lasse V. Karlsen
在vtables中,由于有2个虚函数,因此存储了这些信息。 - Raghav55

3
基本上,如果你用来调用 f() 的变量的编译时类型是 Base1,它将调用基础方法 - 因为实际上没有任何东西覆盖它。
如果编译时类型是 Derived1Derived2,它将根据对象的执行时类型在 Derived1Derived2 中调用相应的方法......因为此时编译器将只发出对 Derived1.f() 的虚拟调用,覆盖将在执行时发生。
是的,.NET使用vtable。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接