C#中的非虚方法,静态绑定和接口

时间:2011-08-27 18:20:58

标签: c# interface non-virtual-interface static-binding

据我所知,非虚拟方法静态绑定,这意味着,据我所知,它在编译时就知道哪个方法将在哪个对象上调用。此决定基于对象的静态类型。令我困惑的是 interfaces (而不是 class )和静态绑定。

考虑这段代码,

public interface IA
{
    void f();
}
public class A : IA
{
    public void f() {  Console.WriteLine("A.f()"); }
}
public class B : A 
{
    public new void f() {  Console.WriteLine("B.f()"); }
}

B b = new B();
b.f();  //calls B.f()     //Line 1

IA ia = b as IA;
ia.f(); //calls A.f()     //Line 2

演示代码:http://ideone.com/JOVmi

我理解Line 1。编译器可以知道b.f()将调用B.f(),因为它知道b的{​​em>静态类型B

但编译器如何在编译时决定 ia.f()将调用A.f()?什么是对象ia静态类型?不是IA吗?但那是一个接口,并没有f()的任何定义。怎么会有效呢?

为了让案例更令人费解,让我们考虑一下这个static方法:

static void g(IA ia)
{
   ia.f(); //What will it call? There can be too many classes implementing IA!
}

正如评论所说,可能有太多的类实现了接口IA,那么编译静态如何决定哪个方法ia.f()会调用?我的意思是,如果我将一个类定义为:

public class C : A, IA 
{
    public new void f() { Console.WriteLine("C.f()"); }
}

如您所见,与C不同,B除了从IA派生外,还会实施A。这意味着,我们在这里有不同的行为:

g(new B()); //inside g(): ia.f() calls A.f() as before!
g(new C()); //inside g(): ia.f() doesn't calls A.f(), rather it calls C.f()

演示代码:http://ideone.com/awCor

我如何理解所有这些变化,尤其是接口和静态绑定如何协同工作?

还有更多(ideone):

C c = new C();
c.f(); //calls C.f()

IA ia = c as IA;
ia.f(); //calls C.f()

A a = c as A;
a.f(); //doesn't call C.f() - instead calls A.f()

IA iaa = a as IA;
iaa.f(); //calls C.f() - not A.f()

请帮助我理解所有这些,以及C#编译器如何完成静态绑定。

2 个答案:

答案 0 :(得分:6)

  

但编译器如何在编译时决定 ia.f()将调用A.f()

没有。它知道ia.f()将在IA.f()中包含的对象实例上调用ia。它会发出这个调用操作码,并让运行时在执行调用时将其计算出来。

以下是将为示例代码的下半部分发出的IL:

    .locals init (
            class B   V_0,
            class IA  V_1)
    IL_0000:  newobj instance void class B::'.ctor'()
    IL_0005:  stloc.0
    IL_0006:  ldloc.0
    IL_0007:  callvirt instance void class B::f()
    IL_000c:  ldloc.0
    IL_000d:  stloc.1
    IL_000e:  ldloc.1
    IL_000f:  callvirt instance void class IA::f()
    IL_0014:  ret

请注意,两种情况都使用callvirt。之所以使用它是因为当目标方法是非虚拟的时,运行时能够自行计算出来。 (另外,callvirtthis参数执行隐式空值检查,而call则不执行。{/ p>

此IL转储应该回答您的所有其他问题。简而言之:编译器甚至不会尝试解析最终的方法调用。这是运行时的工作。

答案 1 :(得分:1)

静态绑定意味着你想象的其他东西。也称为“早期绑定”,它与后期绑定相反,在C#版本4中提供了动态关键字,并且在所有带反射的版本中都可用。后期绑定的主要特征是编译器无法验证被调用的方法是否存在,更不用说验证是否传递了正确的参数。如果出现问题,您将获得运行时异常。它也很慢,因为运行时需要做额外的工作来查找方法,验证参数并构造调用堆栈帧。

使用接口或虚拟方法时,这不是问题,编译器可以预先验证所有内容。生成的代码非常高效。这仍然导致实现接口和虚拟方法所需的间接方法调用(也称为“动态调度”),但仍然在C#中用于非虚拟实例方法。来自前C#团队成员的blog post记录。使这项工作的CLR管道称为“方法表”。大致类似于C ++中的v表,但方法表包含每个方法的条目,包括非虚方法。接口引用只是指向此表的指针。