假设我们在 C# 中有以下示例代码:
class BaseClass
{
public virtual void HelloWorld()
{
Console.WriteLine("Hello Tarik");
}
}
class DerivedClass : BaseClass
{
public override void HelloWorld()
{
base.HelloWorld();
}
}
class Program
{
static void Main(string[] args)
{
DerivedClass derived = new DerivedClass();
derived.HelloWorld();
}
}
当我输入以下代码时:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 15 (0xf)
.maxstack 1
.locals init ([0] class EnumReflection.DerivedClass derived)
IL_0000: nop
IL_0001: newobj instance void EnumReflection.DerivedClass::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: callvirt instance void EnumReflection.BaseClass::HelloWorld()
IL_000d: nop
IL_000e: ret
} // end of method Program::Main
但是,csc.exe 转换了derived.HelloWorld();
--> callvirt instance void EnumReflection.BaseClass::HelloWorld()
。这是为什么?我没有在 Main
方法中的任何地方提到 BaseClass。
而且如果它正在调用 BaseClass::HelloWorld()
那么我希望 call
而不是 callvirt
因为它看起来直接调用BaseClass::HelloWorld()
方法。
最佳答案
调用转到 BaseClass::HelloWorld,因为 BaseClass 是定义该方法的类。虚拟分派(dispatch)在 C# 中的工作方式是在基类上调用方法,虚拟分派(dispatch)系统负责确保调用最派生的方法覆盖。
Eric Lippert 的回答非常有用:https://stackoverflow.com/a/5308369/385844
正如他关于该主题的博客系列:http://blogs.msdn.com/b/ericlippert/archive/tags/virtual+dispatch/
Do you have any idea why this is implemented this way? What would happen if it was calling derived class ToString method directly? This way didnt much sense this to me at first glance...
以这种方式实现是因为编译器不跟踪对象的运行时类型,只跟踪它们引用的编译时类型。使用您发布的代码,很容易看出调用将转到该方法的 DerivedClass 实现。但是假设 derived
变量是这样初始化的:
Derived derived = GetDerived();
GetDerived()
有可能返回 StillMoreDerived
的实例。如果 StillMoreDerived
(或继承链中 Derived
和 StillMoreDerived
之间的任何类)覆盖该方法,则调用 派生
方法的实现。
通过静态分析找到一个变量可能持有的所有可能值就是解决停机问题。对于 .NET 程序集,问题更严重,因为程序集可能不是完整的程序。因此,编译器可以合理地证明 derived
不包含对更多派生对象(或空引用)的引用的情况很少。
添加此逻辑以发出 call
而不是 callvirt
指令需要多少成本?毫无疑问,成本将远远高于所获得的微薄 yield 。
关于c# - 为什么 C# 编译器会产生方法调用以在 IL 中调用 BaseClass 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10219188/