据我所知,非虚拟方法静态绑定,这意味着,据我所知,它在编译时就知道哪个方法将在哪个对象上调用。此决定基于对象的静态类型。令我困惑的是 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
我理解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()
我如何理解所有这些变化,尤其是接口和静态绑定如何协同工作?
还有更多(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#编译器如何完成静态绑定。
答案 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
。之所以使用它是因为当目标方法是非虚拟的时,运行时能够自行计算出来。 (另外,callvirt
对this
参数执行隐式空值检查,而call
则不执行。{/ p>
此IL转储应该回答您的所有其他问题。简而言之:编译器甚至不会尝试解析最终的方法调用。这是运行时的工作。
答案 1 :(得分:1)
静态绑定意味着你想象的其他东西。也称为“早期绑定”,它与后期绑定相反,在C#版本4中提供了动态关键字,并且在所有带反射的版本中都可用。后期绑定的主要特征是编译器无法验证被调用的方法是否存在,更不用说验证是否传递了正确的参数。如果出现问题,您将获得运行时异常。它也很慢,因为运行时需要做额外的工作来查找方法,验证参数并构造调用堆栈帧。
使用接口或虚拟方法时,这不是问题,编译器可以预先验证所有内容。生成的代码非常高效。这仍然导致实现接口和虚拟方法所需的间接方法调用(也称为“动态调度”),但仍然在C#中用于非虚拟实例方法。来自前C#团队成员的blog post记录。使这项工作的CLR管道称为“方法表”。大致类似于C ++中的v表,但方法表包含每个方法的条目,包括非虚方法。接口引用只是指向此表的指针。