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

时间:2013-01-21 16:39:11

标签: c# language-specifications

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期间,方法调用才会更改。

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

3 个答案:

答案 0 :(得分:6)

这更多的是虚拟方法与非虚方法,以及调用的发生方式。您引用的规范部分处理变量上的方法调用 - 调用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() 引用A的哪个子类,都会导致调用someInst,因为这是一个非虚方法。

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

 someInst.Bar();

将致电B.Bar(),而不是A.Bar()

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

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

答案 1 :(得分:1)

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

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

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)虚拟发送的非常好的三部分系列文章,我强烈建议您阅读它们以更全面地理解该主题。

答案 2 :(得分:0)

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的重载,在您的示例中,基于编译时间变量的类型。