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

发布于 2024-12-02 03:12:28 字数 2022 浏览 1 评论 0原文

我知道非虚拟方法是静态绑定的,这意味着,据我所知,它在编译时本身就知道将在哪个对象上调用哪个方法。该决定是根据对象的静态类型做出的。让我困惑的是接口(而不是)和静态绑定。

考虑这段代码,

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()"); }
}

如您所见,CB 不同,除了派生自 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

橪书 2024-12-09 03:12:28

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

事实并非如此。它知道 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 参数执行隐式 null 检查,而 call 则不会。)

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

But how does the compiler decide at compile-time itself that ia.f() will call A.f()?

It doesn't. It knows that ia.f() will be calling IA.f() on the object instance contained in ia. 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:

    .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

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 the this argument, while call 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.

娇女薄笑 2024-12-09 03:12:28

静态绑定的含义与您想象的不同。也称为“早期绑定”,它与后期绑定相反,在 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文