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#编译器如何完成静态绑定.
但是编译器如何决定在编译时间本身是
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两种情况都使用.之所以使用它是因为当目标方法是非虚拟的时,运行时能够自行计算出来.(另外,callvirt对this参数执行隐式空值检查,而不call执行.)
此IL转储应该回答您的所有其他问题.简而言之:编译器甚至不会尝试解析最终的方法调用.这是运行时的工作.