2010-09-01 133 views
96

声明接口中的C#方法时不使用关键字virtual,并且在派生类中重写而不使用override关键字。为什么C#接口方法未声明为抽象或虚拟?

这是有原因吗?我认为这只是一种语言方便,显然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 virtualfinal

+2

因为它们被声明为接口 – 2010-09-01 19:22:21

+2

@Muad:请参阅我的编辑。 – 2010-09-01 19:24:49

回答

128

对于接口,添加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虚拟,即使编译器必须经过箍做到这一点。

0

他们不是虚拟的(在我们如何看待他们,如果不是在底层实现的条款术语(密封虚拟) - 好:-)

读到这里其他的答案,并了解自己的东西它们不会覆盖任何内容 - 接口中没有实现。

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

这是由类然后实施接口方法,因为它会在合同范围内 - 虚拟或“非虚拟”(密封的虚拟结果)。

+0

此线程中的每个人都知道接口的用途。这个问题非常具体 - IL生成的*对于接口方法是虚拟的,对于非接口方法来说不是虚拟的。 – 2010-09-01 19:30:43

+5

是的,在问题被编辑后批评答案真的很容易,不是吗? – 2010-09-01 19:41:29

66

通过CSHARP第三版从CLR引用杰弗里Ritcher这里

的CLR要求该接口 方法被标记为虚拟的。如果您的 没有在您的源代码中明确标记虚方法为 ,那么 编译器将该方法标记为虚拟 并加密;这可以防止衍生的 类重写接口 方法。如果您明确将 方法标记为虚拟,则编译器将该方法标记为 ,该方法为虚拟方式(并且保留 解除密封);这允许派生类 覆盖接口方法。如果密封接口方法 ,派生类 不能覆盖方法 。但是,派生类可以重新继承相同的接口,并且可以使用 为接口的方法提供自己的实现。

+24

引用并没有说_why_接口方法的实现需要标记为虚拟。这是因为它在接口类型方面是多态的,所以它在_virtual_表上需要一个插槽来允许虚拟方法分派。 – 2010-09-01 20:06:26

+1

我不允许将接口方法明确标记为虚拟,并获取错误“错误CS0106:修饰符'虚拟'对此项无效”。使用v2.0.50727进行测试(我个人电脑上的最旧版本)。 – ccppjava 2013-08-22 10:01:45

+3

@ccppjava从下面的Jorado的评论中,您标记了正在实现接口虚拟的类成员,以允许子类重写该类。 – 2013-09-28 19:22:27

4

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

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

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

10

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

我原来的回答中所欠缺的是最终属性的意义。它可以防止派生类重写虚方法。派生类必须重新实现接口,实现方法shadow的基类方法。这足以实现C#语言合约,它说实现方法不是虚拟的。

如果您将示例类中的Dispose()方法声明为虚拟,则会看到最终的属性被删除。现在让派生类可以覆盖它。

0

<笑话>这是您可能想问问Anders Hejlsberg和其他C#设计团队的问题。 < /笑话>

接口比类更抽象的概念,当你声明一个实现接口的类,你只是说“之类必须从接口这些具体方法,并不要紧wheter 虚拟非虚拟重写,只要它具有相同ID和相同类型的参数”。

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

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

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

相关问题