运行时类型与编译时类型方法调用

C#4.0规范如下:

调用虚方法时,进行该调用的实例的运行时类型决定了要调用的实际方法实现。 在非虚方法调用中,实例的编译时类型是决定因素。

起初,我认为这与初始化有关。 例如,给定两个初始化:

BaseClass bcDerived = new Derived(); vs BaseClass bcBase = new BaseClass();

和辅助类中的重载:

 public virtual void Method(Derived d) { Console.WriteLine("Result = derived called"); } public virtual void Method(BaseClass d) { Console.WriteLine("Result = base called"); } 

在这种情况下, Method调用不受virtual关键字的影响。 无论标记为virtual ,都会调用派生最少的重载。 仅在Derived类中的override期间,方法调用才会更改。

那么,“运行时类型”和“编译时类型”是什么意思呢? 它们如何影响方法调用?

这更多的是虚拟方法与非虚方法,以及调用的发生方式。 您引用的规范部分处理变量上的方法调用 – 调用bcDerived.SomeMethod() ,而不是调用foo.SomeMethod(bcDerived)

您引用的规范是指您具有非虚方法的情况:

 public class A { public void Foo() { Console.WriteLine("A.Foo"); } public virtual void Bar() { Console.WriteLine("A.Bar"); } } public class B : A { public new void Foo() { Console.WriteLine("B.Foo"); } public override void Bar() { Console.WriteLine("B.Bar"); } } 

然后调用的方法将由编译器在编译时确定,这样做:

 A someInst = new B(); someInst.Foo(); 

将导致它调用A.Foo()无论someInst引用A的哪个子类 ,因为这是一个非虚方法。

但是,如果您有虚方法,则编译器会指定callvirt指令,该指令会将决策移至运行时。 这意味着:

  someInst.Bar(); 

将调用B.Bar() ,而不是A.Bar()

在您的情况下,您不是在调用虚拟方法(在规范所指的意义上),而是在进行标准方法解析。 C#规范的7.5.3详细介绍了过载分辨率。 在您的情况下,编译器会检查参数列表( bcDerived ),并将其视为类型BaseClass 。 对此的“最佳匹配”将是public virtual void Method(BaseClass d)因为参数列表直接匹配参数列表,因此在编译时使用。

如果查看规范,方法重载解析不直接使虚方法调用生效 – 它只查看类型之间的隐式转换。

在这种情况下,参数的编译时类型将始终用于确定要调用的重载。 虚拟分派取决于调用该方法的对象的运行时类型。

编译时类型是由编译器确定的对象类型,运行时类型是代码执行时的实际类型。 要使用您的示例:

 BaseClass bcDerived = new Derived() 

编译时类型是BaseClass而运行时类型将是Derived

要了解我们稍微扩展您的课程所需的含义:

 class BaseClass { public virtual void SomeMethod() { Console.WriteLine("In base class"); } } class Derived : BaseClass { public override void SomeMethod() { Console.WriteLine("In derived class"); } } 

现在调用bcDerived.SomeMethod()将取决于bcDerived.SomeMethod()的运行时类型,以确定是调用BaseClass实现还是调用Derived实现。

Eric Lippert撰写了一篇关于.Net虚拟调度的非常好的三部分系列(第一部分在这里 ),我强烈建议阅读它们以更全面地理解这个主题。

 Using these two classes as examples: public class Parent { public void NonVirtual() { Console.WriteLine("Nonvirtual - Parent"); } public virtual void Virtual() { Console.WriteLine("Virtual - Parent"); } } public class Child : Parent { public override void Virtual() { Console.WriteLine("Virtual - Child"); } public void NonVirtual() { Console.WriteLine("Nonvirtual - Child"); } } 

通过此codE可以清楚地看到虚拟和非虚拟之间的区别:

 Parent childAsParent = new Child(); childAsParent.Virtual(); childAsParent.NonVirtual(); 

这打印:

 虚拟 - 儿童
非虚拟 - 父 

在虚拟方法的情况下,它在运行时看到childAsParent的类型是子类,因此执行子的Virtual定义。 对于非虚方法,它看到变量的编译时类型是Parent ,并忽略了实际实例是Child并使用父实现的事实。

virtual不用于根据参数的类型确定方法的哪个重载。 确定要调用的方法的重载总是在编译时完成(当不使用dynamic ),从不在运行时,因此在您的示例中,它将始终根据变量的编译时类型选择Method的重载。