为什么C#接口方法没有被声明为抽象的或虚拟的?
接口中的C#方法是在不使用virtual
关键字的情况下声明的,并且在派生类中override
而不使用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
。
对于接口来说,增加abstract
,甚至是public
关键字是多余的,所以你省略了它们:
interface MyInterface { void Method(); }
在CIL中,该方法被标记为virtual
和abstract
。
(请注意,Java允许将接口成员声明为public abstract
)。
对于实施课程,有一些select:
不可重写 :在C#中,类不声明方法为virtual
。 这意味着它不能在派生类中重写(仅隐藏)。 在CIL中,该方法仍然是虚拟的(但是是封闭的),因为它必须支持关于接口types的多态性。
class MyClass : MyInterface { public void Method() {} }
Overridable :在C#和CIL中,这个方法都是virtual
。 它参与多态调度,可以被覆盖。
class MyClass : MyInterface { public virtual void Method() {} }
显式 :这是类实现接口的一种方式,但不能在类本身的公共接口中提供接口方法。 在CIL中,该方法将是private
(!),但是它仍然可以从类的外部从对相应接口types的引用中调用。 显式实现也是不可覆盖的。 这是可能的,因为有一个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 { }
如果Base
和Derived
是在同一个程序集中声明的,那么即使Base
没有实现这个接口,编译器也会使Base::Method
虚拟的并且被封装(在CIL中)。
如果Base
和Derived
在不同的程序集中,那么在编译Derived
程序集时,编译器不会更改其他程序集,因此它将在Derived
中引入一个成员,这将成为MyInterface::Method
一个显式实现,它将只调用委托到Base::Method
。
所以你看, 每个接口方法的实现都必须支持多态的行为,因此必须在CIL上标记为虚拟,即使编译器必须经过这样的努力。
在这里通过CSharp 3rd Edition从CLR引用Jeffrey Ritcher
CLR要求将接口方法标记为虚拟。 如果你没有在你的源代码中明确地标记这个方法是虚拟的,那么编译器将这个方法标记为虚拟的并且是密封的; 这可以防止派生类重写接口方法。 如果你明确地将这个方法标记为虚拟的,那么编译器将这个方法标记为虚拟的(并且保持它的开启状态)。 这允许派生类覆盖接口方法。 如果一个接口方法被封装,派生类不能覆盖该方法。 但是,派生类可以重新inheritance相同的接口,并可以为接口的方法提供自己的实现。
是的,就运行时而言,接口实现方法是虚拟的。 这是一个实现细节,它使接口工作。 虚拟方法在类的v-表中获得槽,每个槽具有指向其中一个虚拟方法的指针。 将对象转换为接口types将生成一个指向实现接口方法的表部分的指针。 现在使用接口引用的客户端代码会看到接口指针偏移量为0的第一个接口方法指针,等等。
我最初的回答中所欠缺的是最终属性的意义。 它可以防止派生类重写虚方法。 派生类必须重新实现接口,实现方法映射基类的方法。 这足以实现C#语言合约,即实现方法不是虚拟的。
如果您将Example类中的Dispose()方法声明为虚拟,您将看到最终的属性被删除。 现在允许派生类来覆盖它。
在大多数编译的代码环境中,接口被实现为vtables – 指向方法体的指针列表。 通常,一个实现了多个接口的类将在其内部编译器的某个地方生成一个接口vtables的列表,每个接口一个vtable(以便保留方法顺序)。 这也是COM接口通常如何实现的。
但是,在.NET中,接口不是作为每个类的不同的vtables来实现的。 接口方法通过所有接口所属的全局接口方法表进行索引。 因此,为了使这个方法实现一个接口方法,没有必要声明一个虚方法 – 全局接口方法表可以直接指向类方法的代码地址。
为了实现一个接口而声明一个虚拟的方法在其他语言中也是不需要的,即使在非CLR平台中也是如此。 Win32上的Delphi语言就是一个例子。
它们不是虚拟的(就我们如何看待它们而言,如果不是以底层实现的forms来表示的话)(密封虚拟) – 很好的阅读其他答案,并自己学习一些东西:-)
他们不覆盖任何东西 – 界面中没有实现。
所有的接口都是提供一个类必须遵守的“契约” – 一个模式,如果你喜欢的话,那么即使他们以前从来没有见过那个特定的类,调用者也知道如何调用这个对象。
然后由class级来实施界面方法,因为在合同的范围内 – 虚拟的或者“非虚拟的”(事实certificate是虚拟的)。
<笑话>这是你可能想问的问题,安德斯Hejlsberg和其余的C#devise团队。</ joke>
接口是一个比类更抽象的概念,当你声明一个实现一个接口的类时,你只是说“类必须从接口有这些特定的方法,而不pipe静态 , 虚拟 , 非虚拟 , 重写 ,只要它具有相同的ID和相同的types参数“。
支持Object Pascal(“Delphi”)和Objective-C(Mac)等接口的其他语言不需要将接口方法标记为虚拟的而不是虚拟的。
但是,你可能是对的,如果你想限制实现特定接口的类方法,我认为在接口中有一个特定的“虚拟”/“覆盖”属性是个好主意。 但是,这也意味着对于这两个接口都有一个“非虚拟的”,“dontcareifvirtualornot”关键字。
我理解你的问题,因为我在Java中看到类似的东西,当一个类方法必须使用“@virtual”或“@override”来确定一个方法是虚拟的。