运行时类型VS编译时类型的方法调用

问题描述:

的C#4.0规格如下:运行时类型VS编译时类型的方法调用

当调用虚拟方法,该实例的用于 运行时类型其中该调用发生确定实际方法 实施调用。在非虚拟方法调用中,实例的编译时类型是决定性因素。

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

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 invokation不是由在此情况下,virtual关键字影响。无论标记为virtual,调用的是派生最少的超载。只有在Derived类中的override期间,方法调用才会更改。

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

+0

请说明'Method'的声明以及使用方法。 –

+0

@BrianRasmussen - 在OP中,这些方法是辅助类的成员。 –

+1

谢谢。在这种情况下,我不确定接受的答案如何与您的问题相匹配,因为它不包含辅助类。但是,只要你对答案满意,我就很好。 –

这更多的是虚拟与非虚拟方法以及调用的发生方式。您引用的部分规格处理方法调用变量 - 调用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(); 

将导致此通过someInst调用A.Foo()不管是什么子类A的被提及,因为这我这是一种非虚拟的方法。

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

someInst.Bar(); 

会打电话的B.Bar(),不A.Bar()

就你而言,你并没有调用虚拟方法(在规范中指的是),而是在执行标准的方法解析。 C#Spec的7.5.3详细介绍了Overload解析。在你的情况下,参数列表(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至于BaseClass实现是否会被调用或Derived执行将被调用。

Eric Lippert在.Net(其中part one is here)中撰写了关于虚拟调度的很好的三部分系列,我强烈建议阅读它们以更充分地理解该主题。

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"); 
    } 
} 

虚拟和非虚拟之间的差被最清楚地通过此代码看出:

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

此打印:

Virtual - Child 
Nonvirtual - Parent

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

virtual不用于根据参数的类型确定使用哪种方法的过载。确定调用方法的哪个超载始终在编译时完成(不使用dynamic时),从不在运行时,所以它总是会选择Method的超载,在您的示例中,基于编译时间变量的类型。