runtime type vs compile-time type method invocatio

2019-02-16 21:14发布

问题:

The C# 4.0 specs read:

When a virtual method is invoked, the runtime type of the instance for which that invocation takes place determines the actual method implementation to invoke. In a nonvirtual method invocation, the compile-time type of the instance is the determining factor.

At first, I thought this had something to do with initialization. For example, given two initializations:

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

and an overload in a helper class:

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

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

Method invokation is not impacted by the virtual keyword in this case. Regardless of having marked virtual, the least derived overload is called. Only during override in the Derived class does the method invocation change.

So, what do "runtime type" and "compile-time type" mean? How do they impact method invocation?

回答1:

This is more a matter of virtual vs. non-virtual methods, and how the invocation occurs. The portion of the spec you are quoting deals with method calls on a variable - calling bcDerived.SomeMethod(), not calling foo.SomeMethod(bcDerived).

The specification you are quoting refers to the case where you have non-virtual methods:

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

Then the method called will be determined by the compiler, at compile time, so doing:

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

Will cause this to call A.Foo(), no matter what subclass of A is being referred to by someInst, since this is a non-virtual method.

If you have a virtual method, however, the callvirt instruction is specified by the compiler, which moves the decision to runtime. This means that:

 someInst.Bar();

Will call B.Bar(), not A.Bar().

In your case, you're not calling a virtual method (in the sense that the spec is referring to), but doing standard method resolution. 7.5.3 of the C# Spec deals with Overload resolution in detail. In your case, the argument list (bcDerived) is inspected by the compiler, and seen to be defined as type BaseClass. The "best match" for this is going to be public virtual void Method(BaseClass d) as the the parameter list directly matches the argument list, so that is used at compile time.

Method overload resolution, if you look at the specification, doesn't directly take virtual method calls into effect - it only looks at implicit conversions between types.



回答2:

In this instance the compile time type of the argument will always be used to determine which overload to call. Virtual dispatch depends upon the runtime type of the object that the method is being called on.

The compile time type is the type of the object as determined by the compiler and the runtime type is the actual type as the code is executing. To use your example:

BaseClass bcDerived = new Derived()

The compile time type is BaseClass whereas the runtime type will be Derived.

To understand the implications we need to extend your class slightly:

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

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

Now calling bcDerived.SomeMethod() will depend upon the runtime type of bcDerived as to whether the BaseClass implementation will be called or the Derived implementation will be called.

Eric Lippert wrote a very good three part series on virtual dispatch in .Net (of which part one is here), I'd highly recommend reading them to more fully understand the subject.



回答3:

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

The difference between virtual and non-virtual is most clearly seen through this codE:

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

This prints:

Virtual - Child
Nonvirtual - Parent

In the case of the virtual method it sees, at runtime, that the type of childAsParent is a child, and thus executes the child's definition of Virtual. For the non virtual method it sees that the compile time type of the variable is Parent, and ignores the fact that the actual instance is a Child and uses the parent's implementation.

virtual isn't used to determine which overload of a method is used based on the types of the parameters. Determining which overload of a method to call is always done at compile time (when not using dynamic), never at runtime, so it will always choose the overload of Method, in your example, based on the compile time type of the variable.