8

C# 4.0 规范如下:

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

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

BaseClass bcDerived = new Derived();对比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,都会调用派生最少的重载。只有在override派生类中,方法调用才会改变。

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

4

3 回答 3

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();

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

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

 someInst.Bar();

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

在您的情况下,您不是在调用虚拟方法(在规范所指的意义上),而是在执行标准方法解析。C# Spec 的 7.5.3 详细处理了重载解决方案。在您的情况下,编译器会检查参数 list ( bcDerived) ,并将其定义为 type BaseClass。对此的“最佳匹配”将是public virtual void Method(BaseClass d)参数列表直接匹配参数列表,因此在编译时使用。

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

于 2013-01-21T16:43:20.427 回答
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的运行时类型。BaseClassDerived

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

于 2013-01-21T16:41:07.800 回答
1
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();

这打印:

虚拟 - 孩子
非虚拟 - 父级

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

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

于 2013-01-21T16:45:49.627 回答