C#如何区分实例方法和虚拟方法

5
基本上,我想了解C#编译器确定所调用的方法是非虚拟实例方法还是虚拟方法的一般步骤。
这两个解释(CLR via C#第3版,Jeffrey Richter,第4章类型基础)引起了混淆。
对于非虚拟实例方法的调用,JIT编译器会定位与用于进行调用的变量类型相对应的类型对象。
而对于虚拟方法的调用,JIT编译器会在方法中生成一些额外的代码,每次调用该方法时都会执行此代码。此代码将首先查找用于进行调用的变量,然后跟随地址到调用对象。
我创建了一个小测试项目。
class Program
    {
        static void Main(string[] args)
        {
            Parent p = new Derived();
            p.Foo(10); // Outputs Derived.Foo(int x)

            Derived d = new Derived();
            d.Foo(10); // Outputs Derived.Foo(double y)
        }
    }

    internal class Parent
    {
        public virtual void Foo(int x)
        {
            Console.WriteLine("Parent.Foo(int x");
        }
    }

    internal class Derived: Parent
    {
        public override void Foo(int x)
        {
            Console.WriteLine("Derived.Foo(int x)");
        }

        public void Foo(double y)
        {
            Console.WriteLine("Derived.Foo(double y)");
        }
    }

虽然Jon Skeet在他的博客文章中解释了程序为什么会产生这些输出,Eric Lippert也在他的博客文章中确认了这一点(请查看评论部分),但我仍然无法弄清楚编译器是如何决定调用的方法是非虚拟实例方法还是虚拟方法。
似乎对于非虚拟实例方法调用,编译器检查用于调用方法的变量的类型,而对于虚拟方法,则检查用于调用方法的变量引用的对象的类型,因此我猜应该有一种方法可以确定方法是非虚拟的还是虚拟的,在决定如何执行方法之前。

1
确定方法是非虚拟的还是虚拟的 - 虚拟方法声明为 virtual/override,非虚拟方法则没有声明。 - dtb
当然有。编译器了解它所处理的类型的所有信息,因此我猜应该有某种方法来确定... - H H
@dtb 是的,但是 d.Foo() 并没有说明虚拟/重写。 - Michael
@HenkHolterman 听起来很不错,那么编译器是查看d变量的类型还是它引用的对象呢? - Michael
它考虑了引用(变量)和实例的两种类型,然后应用规则。 - H H
2
就拿这种方式来考虑吧。编译器得到的是你正在阅读的相同源代码。你能确定方法调用是指虚拟、实例还是静态方法吗?你能编写一个执行同样工作的程序吗?那么你就可以编写编译器的这部分。当然,编译器具体如何实现是一个实现细节。 - Eric Lippert
1个回答

10
在决定如何执行方法之前,应该有一些方法来确定方法是非虚拟还是虚拟的。
JIT编译器与C#编译器不同。当执行到达JIT编译器时,C#编译器已经决定是否发出虚拟方法调用或非虚拟方法调用。Richter书所告诉你的唯一是JIT编译器调用虚拟和非虚拟方法的不同方式。它并没有告诉你C#编译器如何决定是否发出虚拟或非虚拟方法调用。
一般来说,你需要查看语言规范来实现这个目标。如果编译器解释程序代码调用的方法是虚拟的,它将发出虚拟调用。否则,它将不会。在这种特殊情况下,也就是Jon帖子的要点,语言规范的规则指示编译器对第二次调用中的Derived.Foo(double)发出非虚拟方法调用。这是因为在这种情况下,编译器根据语言规范解释程序代码调用Derived.Foo(double)。JIT不知道任何这些信息,它仅仅看到将使用引用d作为隐式this参数和10作为第一个显式参数执行对Derived.Foo(double)的非虚拟调用的IL。

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