使用多态时用于重载方法的ILDASM代码

时间:2019-01-21 05:50:45

标签: c# polymorphism override

当我执行以下代码时,ILDASM中将显示以下内容。我的疑问是,即使将执行子类方法,ILDASM为什么仍显示已调用Baseclass方法。是否因为实例的类型为BaseClass?如果是原因,那么基类如何访问子类方法?

 class BaseClass
    {
        public virtual void Mover(long a,long b)
        {
           Console.WriteLine(a*b+"LongBaseClass");
        }
    }
class ChildClass : BaseClass
{
    public override void Mover(long a, long b)
    {
        Console.WriteLine(a+b+"LongChildClass");
    }
    public static void Main(string[] args)
    {
       long a=10,b=20;
       BaseClass bcc=new ChildClass();
       bcc.Mover(a,b);
}
callvirt instance void practiceonly.BaseClass::Mover(int64, int64)

2 个答案:

答案 0 :(得分:1)

callvirt IL代码用于虚拟方法,以确保调用正确的重写。对于callcallvirt here之间的区别,有相当好的解释,或者还有一些更技术性的讨论here

每个类都有一个方法表(称为虚拟方法表或VTable),其中包含指向在类及其父级中定义的每个方法要执行的确切代码的指针。与其在层次结构中为方法的每个重载版本包含一个条目,它都为每个方法签名包含一个方法指针。当类重写虚拟方法时,该类在方法的插槽中获得不同的方法指针。

callvirtcall将方法元数据标记作为参数,并使用该标记的属性来确定所需的方法以及如何定位它。 callvirt将使用VTable基于提供的方法元数据来定位要使用的方法,并始终获取对调用该方法的对象类型有效的最衍生版本。另一方面,call将调用您指定的方法,当您需要执行base.Mover(a, b)而不会陷入无限循环时,此方法很有用。

因此,要找到当前最佳的重载,我们只需要原始virtualabstract定义的元数据,因为所有替代将在VTable中占据各自类的相同插槽。唯一会使用除基数以外的任何东西的时间是当您使用call调用特定的替代项时。


不过,只是让所有人都感到困惑,似乎C#将使用callvirt来调用引用类型上的几乎所有非静态方法,除了base.method()类型电话。类实例上的所有常规方法调用(包括属性get / set)都使用callvirt。从C#in 1999的早期原型开发开始就一直采用这种方式,并且几乎可以肯定(有些模糊语言)是由于callvirtthis指针进行了额外检查以确保不是null

答案 1 :(得分:1)

  

[E]尽管将执行子类方法,但ILDASM为什么仍显示调用了Baseclass方法?

因为ILDASM在编译时显示了代码,并且方法覆盖在运行时 得到了解决。不管您是否打来电话

BaseClass baseClass = new ChildClass();
baseClass.Mover(42, 42); // Yields 1764LongBaseClass

ChildClass childClass = new ChildClass();
childClass.Mover(42, 42); // Yields 1764LongChildClass

都将翻译为

callvirt instance void BaseClass::Mover(int64, int64)

这是因为编译器不知道是否存在重写的方法以及您是否打算调用它。

在运行时根据引用所引用的对象确定实际调用的方法(在后一个代码示例中,引用的类型为ChildClass,而在第一个引用中的类型为{{1 }}。


在.NET中,静态编译时多态动态或< strong>运行时多态性。方法重载是动态多态性。静态多态的一个例子是方法重载。