为什么C#接口方法没有声明为抽象或虚拟?

时间:2010-09-01 19:21:36

标签: c# methods interface abstract virtual-functions

接口中的C#方法在不使用virtual关键字的情况下声明,并在派生类中重写,而不使用override关键字。

这有什么理由吗?我认为这只是一种语言方便,显然CLR知道如何处理这个问题(默认情况下方法不是虚拟的),但还有其他技术原因吗?

以下是派生类生成的IL:

class Example : IDisposable {
    public void Dispose() { }
}

.method public hidebysig newslot virtual final 
        instance void  Dispose() cil managed
{
  // Code size       2 (0x2)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ret
} // end of method Example::Dispose

请注意,该方法在IL中声明为virtual final

6 个答案:

答案 0 :(得分:139)

对于界面,添加abstract甚至是public关键字都是多余的,因此请省略它们:

interface MyInterface {
  void Method();
}

在CIL中,该方法标记为virtualabstract

(请注意,Java允许将接口成员声明为public abstract)。

对于实现类,有一些选项:

不可覆盖:在C#中,类不会将方法声明为virtual。这意味着它不能在派生类中重写(仅隐藏)。在CIL中,该方法仍然是虚拟的(但是密封的),因为它必须支持关于接口类型的多态性。

class MyClass : MyInterface {
  public void Method() {}
}

可覆盖:在C#和CIL中,方法都是virtual。它参与多态分派,可以被覆盖。

class MyClass : MyInterface {
  public virtual void Method() {}
}

显式:这是一种类实现接口但不在类本身的公共接口中提供接口方法的方法。在CIL中,该方法将是private(!),但它仍然可以从类外部从对应的接口类型的引用中调用。显式实现也是不可覆盖的。这是可能的,因为有一个CIL指令(.override)将私有方法链接到它正在实现的相应接口方法。

[C#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}

在VB.NET中,您甚至可以在实现类中使用别名接口方法名称。

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}

现在,考虑一下这个奇怪的案例:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }

如果BaseDerived在同一个程序集中声明,编译器会使Base::Method虚拟并密封(在CIL中),即使Base没有实现界面。

如果BaseDerived在不同的程序集中,则在编译Derived程序集时,编译器不会更改其他程序集,因此它将在{{1这将是Derived的显式实现,它只会将调用委托给MyInterface::Method

所以你看,每个接口方法实现都必须支持多态行为,因此必须在CIL上标记为虚拟,即使编译器必须通过箍来完成它。

答案 1 :(得分:71)

通过CSharp第3版从CLR引用Jeffrey Ritcher

  

CLR需要该接口   方法被标记为虚拟。如果你   不要将方法明确标记为   虚拟的源代码,   编译器将该方法标记为虚拟   并密封;这可以防止派生   来自覆盖接口的类   方法。如果你明确标记了   方法为虚拟,编译器标记   该方法为虚拟(并留下它   启封);这允许派生类   覆盖接口方法。如果   接口方法是密封的,a   派生类无法覆盖   方法。但是,派生类可以   重新继承相同的接口,可以   提供自己的实现   界面的方法。

答案 2 :(得分:11)

是的,就运行时而言,接口实现方法是虚拟的。它是一个实现细节,它使接口工作。虚方法获取类'v-table中的槽,每个槽都有一个指向其中一个虚方法的指针。将对象转换为接口类型会生成指向实现接口方法的表部分的指针。使用接口引用的客户端代码现在可以看到第一个接口方法指针位于偏离接口指针的位置,等等。

我在原始答案中未充分理解的是 final 属性的重要性。它可以防止派生类重写虚方法。派生类必须重新实现接口,实现方法 shadow 基类方法。这足以实现C#语言合同,该合同表明实现方法不是虚拟的。

如果将Example类中的Dispose()方法声明为virtual,则会看到 final 属性被删除。现在允许派生类覆盖它。

答案 3 :(得分:4)

在大多数其他编译代码环境中,接口实现为vtable - 一个指向方法体的指针列表。通常,实现多个接口的类将在其内部编译器生成的元数据中的某处具有接口vtable列表,每个接口一个vtable(以便保留方法顺序)。这也是通常实现COM接口的方式。

但是,在.NET中,接口不是作为每个类的不同vtable实现的。接口方法通过全局接口方法表索引,所有接口都是其中的一部分。因此,没有必要声明方法virtual以使该方法实现接口方法 - 全局接口方法表可以直接指向类方法的代码地址。

即使在非CLR平台中,其他语言也不需要声明方法虚拟以实现接口。 Win32上的Delphi语言就是一个例子。

答案 4 :(得分:0)

它们不是虚拟的(就我们如何看待它们而言,如果不是在底层实现方面(密封虚拟) - 很高兴在这里阅读其他答案并自己学习: - )

它们不会覆盖任何内容 - 界面中没有实现。

所有接口都提供了一个类必须遵守的“契约” - 一个模式,如果你愿意,这样调用者就知道如何调用该对象,即使他们以前从未见过那个特定的类。

然后由类来实现接口方法,在合同的范围内 - 虚拟或“非虚拟”(密封虚拟,结果)。

答案 5 :(得分:0)

<笑话>这是你可能想要问Anders Hejlsberg和其他C#设计团队的事情。< / joke>

接口是一个比类更抽象的概念,当你声明一个实现接口的类时,你只是说“类必须从接口中拥有这些特定的方法,并且无关紧要 static 虚拟非虚拟覆盖,只要它具有相同的ID和相同的类型参数“。

支持Object Pascal(“Delphi”)和Objective-C(Mac)等接口的其他语言也不要求将接口方法标记为虚拟而非虚拟。

但是,你可能是对的,我认为在接口中有一个特定的“虚拟”/“覆盖”属性可能是个好主意,以防你想要限制实现的类方法一个特定的界面。但是,这也意味着两个接口都有一个“非虚拟”,“dontcareifvirtualornot”关键字。

我理解你的问题,因为我在Java中看到类似的东西,当类方法必须使用“@virtual”或“@override”时,确保方法是虚拟的。