比方说,我们有以下的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();
}
}
当我ildasmed下面的代码:
.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()
这是为什么? 我没有在任何地方的提BaseClass的Main
方法。
而且如果调用BaseClass::HelloWorld()
那么我会想到call
,而不是callvirt
,因为它看起来直接呼叫到BaseClass::HelloWorld()
方法。
该呼叫转到BaseClass的:: HelloWorld的,因为BaseClass的是定义方法的类。 虚拟调度在C#中的工作方式是,该方法被调用基类,并且虚拟调度系统是负责确保该法的最底层派生覆盖被调用。
的埃里克利珀的这个答案是非常丰富: https://stackoverflow.com/a/5308369/385844
正如他在博客专题系列: http://blogs.msdn.com/b/ericlippert/archive/tags/virtual+dispatch/
你有这是为什么以这种方式实现的任何想法? 如果它是直接调用派生类的ToString方法会发生什么? 这样didnt多大意义,这对我第一眼...
这是因为编译器不跟踪物体的运行时类型,只是它们引用的编译时类型这种方式来实现。 随着你发布的代码,可以很容易地看到,呼叫将转到DerivedClass实施方法。 但假设derived
变量是这样的初始化:
Derived derived = GetDerived();
这有可能是GetDerived()
返回的实例StillMoreDerived
。 如果StillMoreDerived
(或之间的任何类Derived
和StillMoreDerived
继承链)覆盖方法,那么这将是不正确调用Derived
的方法实现。
为了找到一个变量可以通过静态分析容纳所有可能的值是解决停机问题。 随着.NET程序集,这个问题更是雪上加霜,因为组件可能不是一个完整的程序。 那么,情况编译器可以合理证明的数量derived
不坚持为更多的衍生对象(或空引用)的引用会很小。
这多少费用增加这个逻辑,所以它可以发出call
,而不是callvirt
的指令? 毫无疑问,成本将远远高于小利而得。
思考这个问题的方法是虚方法定义一个“插槽”,可以在运行时把一个方法进入。 当我们发出我们说一个callvirt指令“在运行时,看看,看看是什么在这个插槽并调用它”。
所述槽由有关声明的虚拟方法,而不是重写它的类型的类型的方法的信息来标识。
这将是完全合法的发射callvirt到派生方法; 运行时会意识到所导出的方法是相同的时隙为基准的方法和结果将是完全一样的。 但从来没有任何理由这样做。 如果我们通过识别声明插槽类型识别槽更清晰。
需要注意的是这种情况,即使你声明DerivedClass
作为sealed
。
C#使用callvirt
操作者调用任何实例方法( virtual
或没有)自动获取的对象引用空校验-提出一个NullReferenceException
在一个方法调用点。 否则, NullReferenceException
将只在第一次实际使用方法中的类的任何实例成员,其可以是令人惊讶的提高。 如果没有使用实例成员,这一方法实际上能顺利完成而没有提高例外。
你也应该记住,IL不直接执行。 这是第一次由JIT编译器编译为本地指令 - 和执行一些具体取决于您正在调试的过程优化。 我发现,对于CLR 2.0 x86的JIT内联非虚方法,但称为虚拟方法-这也是内联Console.WriteLine
!