问题 接口继承多个接口:这是如何由C#编译器处理的?


最近我发现C#允许

接口可以从一个或多个基接口继承

例如, IScreen 在Caliburn.Micro这样做 http://caliburnmicro.codeplex.com/SourceControl/latest#src/Caliburn.Micro/IScreen.cs

namespace Caliburn.Micro
{
    public interface IScreen : IHaveDisplayName, IActivate, IDeactivate, 
        IGuardClose, INotifyPropertyChangedEx
    {
    }
}

我明白为什么这很有用,因为它意味着一个类实现 IScreen 还需要实现其他接口。

但我想知道C#如何处理编译器和运行时明智。

这个问题的一些背景/背景:

我来自interface定义方法表的背景,实现接口的类既有自己的方法表,也有指向它们实现的接口的方法表的指针。

我脑子里浮现的子问题源于我过去与人们进行的各种多类继承讨论,我认为它们也适用于这种情况:

  • 让接口能够从多个基接口继承,该表中的方法顺序如何?
  • 如果这些接口具有共同的祖先会怎样:这些方法会在表中多次出现吗?
  • 如果这些接口具有不同的祖先,但类似的方法名称怎么办?

(我在这里使用了单词methods,暗示接口中定义的属性将具有get_或set_方法)。

非常感谢对此的任何见解,以及如何更好地表达这个问题的提示。


5547
2017-11-14 10:09


起源

您对CLR中运行时如何表示这个问题有疑问吗?或者编译器如何强制执行它? - Simon Whitehead
所有项目符号仅适用于具体类,接口不继承任何实现,没有表,也没有方法。接口方法到实现它的具体方法的映射是在运行时动态完成的,并且在调用方法之前不会发生。下面的管道非常庞大,是CLR中使用汇编代码的少数几个地方之一。 - Hans Passant
@HansPassant:这些稍纵即逝的瞥见你提供的内部深处令人抓狂! :-)你能提供相关CLR资源的链接吗?你从哪里得到这些信息?如果是原始研究,您是否在任何地方发布结果?谢谢! - Jon
@HansPassant +1;谢谢你。我最初来自本机端的一部分,其中接口具有方法表以便具有COM兼容性。 - Jeroen Wiert Pluimers


答案:


首先,让我们明确地说“接口继承”与基于类的继承并不完全相同(并且使用“继承”这两个词可能会产生误导)。

那是因为接口无法自己实例化,因此编译器/运行时对不必跟踪如何为独立接口类型进行虚拟调用(例如,您不需要知道如何调用 IEnumerable.GetEnumerator  - 你只需要知道如何调用它 在特定类型的对象上)。这允许在编译时以不同方式处理事物。

现在我实际上并没有 知道 编译器如何实现“接口继承”,但这是如何做到的:

让接口能够从多个基接口继承,   该表中方法的顺序如何?

“派生”接口没有必要拥有一个方法表,该表包含来自其所有祖先接口的方法,因为它实际上并不实现它们中的任何一个。每个接口类型只有一个自己定义的方法表就足够了。

如果这些接口具有共同的祖先会是什么:那些方法   在表中多次出现?

鉴于上一个问题的答案,没有。最后,具体类型只会实现 IFoo 只需一次,无论多少次 IFoo 出现在已实现接口的“层次结构”中。方法定义于 IFoo只会出现在 IFoo的簿记表。

如果这些接口具有不同的祖先,但类似的方法会怎样   名字呢?

再说一遍,没问题。您需要适当的语法来告诉编译器“这里是如何实现的 IFoo.Frob 而且这里 IBar.Frob“,但是因为方法 IFoo 和 IBar 将在单独的表中映射,没有技术问题。

当然,这留下了“运行时如何调度方法?”的问题。无人接听。但想象一个可能的解决方案并不难:每种具体类型 C 指向每个实现的接口的方法表。当进行虚拟方法调用时,运行时会查看具体类型,找到要调用其方法的接口的表(接口的类型是静态已知的)并进行调用。


8
2017-11-14 10:15



谢谢你。我来自本地世界中接口必须能够映射到COM接口的地方,所以他们有表(并且不能从多个基接口继承)。你的答案非常有启发性,但我仍然想知道CLR如何在每个接口上有多个vtable。我碰到 msdn.microsoft.com/en-us/magazine/cc163791.aspx#S12 这是.NET 1.1的事态,和 monoruntime.wordpress.com/2009/04/22/...这是单声道,并且似乎都不支持继承多个基类型的接口。 - Jeroen Wiert Pluimers


我无法谈论官方CLR如何做到这一点......但是Rotor发行版在对象vtable中积极地将公共接口祖先叠加在一起。它还在适当的情况下将额外的SLOT分配到具体对象vtable中,从而减少了从具体类型跳转到接口vtable然后再到实现的需要。方法偏移在JIT时间计算。如果无法执行此优化,则单个方法可以多次占用vtable。

所以答案是(关于Rotor无论如何),它确实是一个实现细节,任何重叠/优化等都完全取决于编译器在编译类型时最好的决定。


1
2017-11-14 10:36