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
我理解 第 1 行< /代码>。编译器可以知道
bf()
将调用 Bf()
,因为它知道 b
的静态类型是B
。
但是编译器如何在编译时本身决定 ia.f()
将调用 Af()
?对象 ia
的静态类型是什么?这不是IA
吗?但那是一个接口,并且没有任何 f()
的定义。那它怎么起作用呢?
为了让情况更令人费解,让我们考虑一下这个static
方法:
static void g(IA ia)
{
ia.f(); //What will it call? There can be too many classes implementing IA!
}
正如注释所说,实现接口IA
的类可能太多,那么如何编译< em>静态地决定ia.f()
将调用哪个方法?我的意思是,假设我有一个类定义为:
public class C : A, IA
{
public new void f() { Console.WriteLine("C.f()"); }
}
如您所见,C
与 B
不同,除了派生自 IA
之外,还实现了 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# 编译器如何完成静态绑定。
I understand that non-virtual methods are statically bound, which means, as far as I understand, that its known at compile-time itself as to which method will be invoked on which object. This decision is taken based on the static type of the object(s). What confuses me is interfaces (rather than class) and static binding.
Consider this code,
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
Demo code: http://ideone.com/JOVmi
I understand the Line 1
. The compiler can know that b.f()
will invoke B.f()
because it knows the static type of b
which is B
.
But how does the compiler decide at compile-time itself that ia.f()
will call A.f()
? What is the static type of object ia
? Is it not IA
? But then that is an interface, and doesn't have any definition of f()
. Then how come it works?
To make the case more puzzling, lets consider this static
method:
static void g(IA ia)
{
ia.f(); //What will it call? There can be too many classes implementing IA!
}
As the comment says, there can be too many classes that implement the interface IA
, then how can the compile statically decide which method ia.f()
would call? I mean, say if I've a class defined as:
public class C : A, IA
{
public new void f() { Console.WriteLine("C.f()"); }
}
As you see, C
, unlike B
, implements IA
in addition to deriving from A
. That means, we've a different behavior here:
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()
Demo code : http://ideone.com/awCor
How would I understand all these variations, especially how interfaces and static binding together work?
And few more (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()
Please help me understanding all these, and how static binding is done by the C# compiler.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
事实并非如此。它知道
ia.f()
将在ia
中包含的对象实例上调用IA.f()
。它发出此调用操作码,并让运行时计算出调用何时执行。以下是示例代码下半部分将发出的 IL:
请注意,在这两种情况下都使用了
callvirt
。使用此方法是因为运行时能够自行确定目标方法何时为非虚拟方法。 (此外,callvirt
对this
参数执行隐式 null 检查,而call
则不会。)此 IL 转储应该回答您的所有其他问题问题。简而言之:编译器甚至不会尝试解析最终的方法调用。这是运行时的工作。
It doesn't. It knows that
ia.f()
will be callingIA.f()
on the object instance contained inia
. It emits this call opcode and lets the runtime figure it out when the call is executed.Here is the IL that will be emitted for the bottom half of your example code:
Note that
callvirt
is used in both cases. This is used because the runtime is able to figure out on its own when the target method is non-virtual. (Additionally,callvirt
performs an implicit null check on thethis
argument, whilecall
does not.)This IL dump should answer all of your other questions. In short: the compiler doesn't even attempt to resolve the final method call. That's a job for the runtime.
静态绑定的含义与您想象的不同。也称为“早期绑定”,它与后期绑定相反,在 C# 版本 4 中使用 dynamic 关键字以及所有带有反射的版本中提供。后期绑定的主要特征是编译器无法验证被调用的方法是否存在,更不用说验证是否传递了正确的参数。如果出现任何问题,您将收到运行时异常。它也很慢,因为运行时需要做额外的工作来查找方法、验证参数并构造调用堆栈帧。
当您使用接口或虚拟方法时,这不是问题,编译器可以预先验证所有内容。生成的代码非常高效。这仍然会导致实现接口和虚拟方法所需的间接方法调用(也称为“动态调度”),但在 C# 中仍然用于非虚拟实例方法。记录在此 来自前 C# 团队成员的博客文章。完成这项工作的 CLR 管道称为“方法表”。大致类似于 C++ 中的 v 表,但方法表包含每个方法的条目,包括非虚拟方法。接口引用只是指向该表的指针。
Static binding means something else than you think. Also called 'early binding', it is the opposite of late binding, available in C# version 4 with the dynamic keyword and in all versions with reflection. The main characteristic of late binding is that the compiler cannot verify that the called method even exists, let alone verify that the proper arguments are passed. If something is a-miss, you'll get a runtime exception. It is also slow because the runtime needs to do extra work to lookup the method, verify the arguments and construct the call stack frame.
This is not an issue when you use interfaces or virtual methods, the compiler can verify everything up front. The resulting code is very efficient. This still results in indirect method calls (aka 'dynamic dispatch'), required to implement interfaces and virtual methods, but still used in C# for non-virtual instance methods as well. Documented in this blog post from a former C# team member. The CLR plumbing that makes this work is called a 'method table'. Roughly analogous to a v-table in C++, but the method table contains an entry for every method, including non-virtual ones. An interface reference is simply a pointer into this table.