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

Naw*_*waz 2 c# interface non-virtual-interface static-binding

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

考虑一下这段代码

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
Run Code Online (Sandbox Code Playgroud)

演示代码: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!
}
Run Code Online (Sandbox Code Playgroud)

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

public class C : A, IA 
{
    public new void f() { Console.WriteLine("C.f()"); }
}
Run Code Online (Sandbox Code Playgroud)

如你所见,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()
Run Code Online (Sandbox Code Playgroud)

演示代码: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()
Run Code Online (Sandbox Code Playgroud)

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

cdh*_*wie 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
Run Code Online (Sandbox Code Playgroud)

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

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