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

C# C语言中的非虚方法、静态绑定和接口#,c#,interface,non-virtual-interface,static-binding,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() {

我知道非虚方法是静态绑定的,这意味着,据我所知,编译时就知道在哪个对象上调用哪个方法。此决定基于对象的静态类型作出。让我困惑的是接口(而不是类)和静态绑定

考虑一下这个代码

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
演示代码:

我理解
第1行
。编译器可以知道
b.f()
将调用
b.f()
,因为它知道
b
的静态类型,即
b

但是编译器如何在编译时自己决定
ia.f()
将调用
A.f()
?对象的静态类型是什么?它不是
IA
?但是那是一个接口,没有任何
f()
的定义。那它是怎么工作的呢

<> >为了让这个案例更令人困惑,让我们考虑一下这个代码>静态< /COD>方法:

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
不同,除了从
A
派生之外,还实现了
IA
。这意味着,我们在这里有不同的行为:

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()
演示代码:

我如何理解所有这些变化,特别是接口和静态绑定如何一起工作

还有几个():

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

但是编译器如何在编译时自己决定
ia.f()
将调用
A.f()

没有。它知道
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
在这两种情况下都使用。之所以使用这种方法,是因为运行时能够自行确定目标方法何时为非虚拟方法。(另外,
callvirt
参数执行隐式空检查,而
call
则不执行。)


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

静态绑定的含义与您想象的不同。也称为“早期绑定”,它与后期绑定相反,在C#version 4中使用dynamic关键字,在所有版本中使用reflection。后期绑定的主要特征是编译器无法验证被调用的方法是否存在,更不用说验证是否传递了正确的参数。如果有什么未命中,您将得到一个运行时异常。它也很慢,因为运行时需要做额外的工作来查找方法、验证参数和构造调用堆栈框架


当您使用接口或虚拟方法时,这不是问题,编译器可以预先验证所有内容。生成的代码非常高效。这仍然会导致间接方法调用(也称为“动态分派”),这是实现接口和虚拟方法所必需的,但在C#中仍然用于非虚拟实例方法。这是一位前C#团队成员的记录。使此工作的CLR管道称为“方法表”。大致类似于C++中的V表,但是方法表包含每个方法的条目,包括非虚拟的。接口引用只是指向此表的指针。

静态绑定在C#中意味着什么?它意味着在编译时将调用解析为特定方法(无论是否为接口方法),而不是简单地发出方法名并让运行时解决所有问题。在这种情况下,调用静态绑定到特定的接口方法。编译器不能希望知道它将在接口场景中调用的对象类型。虚拟方法也是如此。编译器可以确定最终方法调用的唯一场景是在非接口(类或结构)类型变量上调用非虚拟方法时。
    .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