2011-08-27 67 views
2

据我所知,非虚方法是静态绑定,这就意味着,就我所知,它在编译时本身已知哪个方法会被调用在哪个对象上。该决定基于对象类型的静态。令我困惑的是接口(而不是)和静态绑定。C#中的非虚方法,静态绑定和接口#

考虑以下代码,

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这是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,除了实现IAA获得。这意味着,我们在这里已经出不同的行为:

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#编译器完成。

回答

6

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

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

这里是将要发出的为您的示例代码下半部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不会。)

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

+0

*静态绑定*在C#中的含义是什么呢? – Nawaz

+0

这意味着该调用在编译时被解析为特定的方法(不管是否为接口方法),而不是简单地发出方法*名称*并让运行时图形*全部*出来。在这种情况下,该调用静态绑定到*特定*接口方法。编译器无法知道它将在接口场景中调用哪个对象类型。这同样适用于虚拟方法。编译器*可以确定最终方法调用的唯一场景是在非接口(类或结构)类型变量上调用非虚方法时。 – cdhowie

1

静态绑定意味着别的东西比你想象的。也称为“早期绑定”,它与后期绑定相反,可在C#版本4中使用动态关键字和所有带反射的版本。后期绑定的主要特点是编译器无法验证被调用的方法是否存在,更不用说验证是否传递了正确的参数。如果有什么是遗漏的,你会得到一个运行时异常。它也很慢,因为运行时需要做额外的工作来查找方法,验证参数并构造调用堆栈框架。

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