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

Posted

技术标签:

【中文标题】为啥 C# 接口方法没有声明为抽象或虚拟的?【英文标题】:Why are C# interface methods not declared abstract or virtual?为什么 C# 接口方法没有声明为抽象或虚拟的? 【发布时间】:2011-04-06 23:46:39 【问题描述】:

接口中的 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

【问题讨论】:

【参考方案1】:

接口是一个比类更抽象的概念,当你声明一个实现接口的类时,你只是说“类必须具有接口中的这​​些特定方法,无论是静态virtualnon virtualoverriden,只要有相同的ID和相同的类型参数”。

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

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

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

【讨论】:

@override 实际上并没有改变代码的行为或改变生成的字节码。它的作用是向编译器发出这样修饰的方法旨在成为覆盖的信号,这允许编译器进行一些健全性检查。 C# 的工作方式不同; override 是语言本身的一级关键字。【参考方案2】:

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

CLR 需要该接口 方法被标记为虚拟的。如果你 不要将方法显式标记为 源代码中的虚拟, 编译器将该方法标记为虚拟 并密封;这可以防止派生 类从覆盖接口 方法。如果您明确标记 方法为虚拟,编译器标记 该方法是虚拟的(并离开它 未密封);这允许派生类 覆盖接口方法。如果 一个接口方法是密封的,一个 派生类不能覆盖 方法。但是,派生类可以 重新继承同一个接口,可以 提供自己的实现 接口的方法。

【讨论】:

引用并没有说明为什么接口方法实现需要标记为虚拟。这是因为它在接口类型方面是多态的,所以它需要 virtual表上的一个槽来允许虚拟方法调度。 我不允许将接口方法显式标记为虚拟,并得到错误“错误CS0106:修饰符'virtual'对此项无效”。使用 v2.0.50727(我电脑上的最旧版本)进行测试。 @ccppjava 在下面 Jorado 的评论中,您将实现接口 virtual 的类成员标记为允许子类覆盖该类。【参考方案3】:

对于界面,添加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程序集时,编译器不会改变另一个程序集,所以它会在Derived中引入一个成员,这将是一个显式实现对于MyInterface::Method,只会将调用委托给Base::Method

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

【讨论】:

【参考方案4】:

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

它们不会覆盖任何东西 - 接口中没有实现。

接口所做的只是提供一个类必须遵守的“契约”——一个模式,如果你喜欢的话,这样调用者就知道如何调用对象,即使他们以前从未见过那个特定的类。

然后由类在合同的范围内实现接口方法 - 虚拟或“非虚拟”(事实证明是密封的虚拟)。

【讨论】:

这个线程中的每个人都知道接口的用途。这个问题非常具体 - IL 生成的 is 对于接口方法是虚拟的,而对于非接口方法不是虚拟的。 是的,问题被编辑后批评答案真的很容易,不是吗?【参考方案5】:

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

我在最初的答案中低估了 final 属性的重要性。它防止派生类覆盖虚拟方法。派生类必须重新实现接口,实现方法shadow基类方法。这足以实现 C# 语言约定,即实现方法不是虚拟的。

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

【讨论】:

【参考方案6】:

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

但是,在 .NET 中,接口并未实现为每个类的不同 vtable。接口方法通过所有接口都属于的全局接口方法表进行索引。因此,不需要为了实现接口方法而声明一个虚拟方法——全局接口方法表可以直接指向类方法的代码地址。

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

【讨论】:

以上是关于为啥 C# 接口方法没有声明为抽象或虚拟的?的主要内容,如果未能解决你的问题,请参考以下文章

C# 中的虚拟/抽象字段

为啥要将 Java 接口方法声明为抽象的?

C#中接口和抽象类

为啥继承具有名称签名的接口成员的 C# 抽象类至少需要实现其中一个?

c#抽象方法:内部公共和虚拟?

抽象类和接口