4

所以基本上我想了解 C# 编译器确定被调用的方法是非虚拟实例方法还是虚拟方法的一般步骤是什么。

混淆来自这两个解释(CLR via C# 3rd edition,Jeffrey Richter,Chapter 4 Type Fundamentals)

调用非虚拟实例方法时,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 在他的博客文章中证实了这一点(查看评论部分),但我仍然无法弄清楚编译器如何决定被调用的方法是否是非虚拟的实例方法或虚拟方法。

似乎对于非虚拟实例方法调用,编译器检查用于调用方法的变量的类型,对于虚拟方法 - 用于调用方法的变量引用的对象类型,所以我猜,在决定如何执行方法之前,应该有一些方法来确定方法是非虚拟的还是虚拟的。

4

1 回答 1

10

在决定如何执行方法之前,应该有一些方法来确定方法是非虚拟的还是虚拟的。

JIT 编译器与 C# 编译器不同。当执行到达 JIT 编译器时,C# 编译器已经决定是发出虚拟方法调用还是非虚拟方法调用。Richter 的书唯一告诉您的是 JIT 编译器在调用虚拟和非虚拟方法时所做的不同事情。它没有告诉您 C# 编译器如何决定是否发出虚拟或非虚拟方法调用。

一般来说,为此,您需要查看语言规范。如果编译器将程序代码解释为调用的方法是虚拟的,它将发出一个虚拟调用。否则,它不会。在这种特殊情况下的语言规则,这就是 Jon 的帖子的重点,要求编译器在第二次调用中发出非虚拟方法Derived.Foo(double)调用。这是因为在这种情况下,编译器将程序代码(再次基于语言规范)解释为调用Derived.Foo(double). JIT 对此一无所知,它只看到 IL 执行非虚拟调用Derived.Foo(double),并将引用d作为隐式this参数和10第一个显式参数。

于 2013-05-31T21:06:05.580 回答