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

Rob*_*vey 106 c# methods virtual-functions interface abstract

接口中的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
Run Code Online (Sandbox Code Playgroud)

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

Jor*_*dão 141

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

interface MyInterface {
  void Method();
}
Run Code Online (Sandbox Code Playgroud)

在CIL中,该方法被标记virtualabstract.

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

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

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

class MyClass : MyInterface {
  public void Method() {}
}
Run Code Online (Sandbox Code Playgroud)

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

class MyClass : MyInterface {
  public virtual void Method() {}
}
Run Code Online (Sandbox Code Playgroud)

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

[C#]

class MyClass : MyInterface {
  void MyInterface.Method() {}
}
Run Code Online (Sandbox Code Playgroud)

[CIL]

.method private hidebysig newslot virtual final instance void MyInterface.Method() cil managed
{
  .override MyInterface::Method
}
Run Code Online (Sandbox Code Playgroud)

在VB.NET中,您甚至可以在实现类中为接口方法名称添加别名.

[VB.NET]

Public Class MyClass
  Implements MyInterface
  Public Sub AliasedMethod() Implements MyInterface.Method
  End Sub
End Class
Run Code Online (Sandbox Code Playgroud)

[CIL]

.method public newslot virtual final instance void AliasedMethod() cil managed
{
  .override MyInterface::Method
}
Run Code Online (Sandbox Code Playgroud)

现在,考虑一下这种奇怪的情况:

interface MyInterface {
  void Method();
}
class Base {
  public void Method();
}
class Derived : Base, MyInterface { }
Run Code Online (Sandbox Code Playgroud)

如果BaseDerived在同一个程序集中声明,编译器将进行Base::Method虚拟和密封(在CIL中),即使Base没有实现接口.

如果Base并且Derived在不同的程序集中,在编译Derived程序集时,编译器将不会更改其他程序集,因此它将引入一个成员,Derived这将是一个显式实现MyInterface::Method,只需委托调用Base::Method.

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


小智 73

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

CLR要求将接口方法标记为虚拟.如果未在源代码中将该方法明确标记为虚拟,则编译器会将该方法标记为虚拟并密封; 这可以防止派生类重写接口方法.如果将方法明确标记为虚拟,则编译器将该方法标记为虚拟(并使其保持未密封状态); 这允许派生类重写接口方法.如果密封了接口方法,则派生类不能覆盖该方法.但是,派生类可以重新继承相同的接口,并可以为接口的方法提供自己的实现.

  • 引用并未说_why_接口方法实现需要标记为虚拟.这是因为它在接口类型方面是多态的,所以_needs_ _virtual_表上的一个插槽允许虚方法调度. (25认同)
  • @ccppjava从下面的Jorado评论中,您标记了实现虚拟接口的类成员,以允许子类覆盖该类. (3认同)

Han*_*ant 12

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

我在原始答案中未充分理解的是最终属性的重要性.它可以防止派生类重写虚方法.派生类必须重新实现接口,实现方法会影响基类方法.这足以实现C#语言契约,该契约表明实现方法不是虚拟的.

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