为什么 C# 接口方法不声明为抽象或虚拟?
接口中的 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
。
C# methods in interfaces are declared without using the virtual
keyword, and overridden in the derived class without using the override
keyword.
Is there a reason for this? I assume that it is just a language convenience, and obviously the CLR knows how to handle this under the covers (methods are not virtual by default), but are there other technical reasons?
Here is the IL that a derived class generates:
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
Notice that the method is declared virtual
final
in the IL.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
对于接口,添加
abstract
,甚至public
关键字都是多余的,因此可以省略它们:在CIL中,该方法被标记为
virtual
和摘要
。(请注意,Java 允许将接口成员声明为
公共抽象
)。对于实现类,有一些选项:
不可重写:在 C# 中,类不会将方法声明为
虚拟
。这意味着它不能在派生类中被重写(只能隐藏)。在 CIL 中,该方法仍然是虚拟的(但密封的),因为它必须支持有关接口类型的多态性。可重写:在 C# 和 CIL 中,该方法都是
虚拟
。它参与多态调度并且可以被覆盖。显式:这是类实现接口但不在类本身的公共接口中提供接口方法的一种方式。在 CIL 中,该方法将是
private
(!),但仍然可以从类外部通过对相应接口类型的引用来调用。显式实现也是不可重写的。这是可能的,因为有一个 CIL 指令 (.override
) 会将私有方法链接到它正在实现的相应接口方法。[C#]
[CIL]
在 VB.NET 中,您甚至可以在实现类中为接口方法名称添加别名。
[VB.NET]
[CIL]
现在,考虑这种奇怪的情况:
如果
Base
和Derived
在同一个程序集中声明,编译器将生成Base::方法
虚拟且密封(在 CIL 中),即使Base
未实现该接口。如果
Base
和Derived
在不同的程序集中,当编译Derived
程序集时,编译器不会改变另一个程序集,因此会引入Derived
中的成员,它将是MyInterface::Method
的显式实现,仅将调用委托给Base::Method
。所以你看,每个接口方法实现都必须支持多态行为,因此必须在 CIL 上标记为 virtual,即使编译器必须经过一番周折才能做到这一点。
For the interface, the addition of the
abstract
, or even thepublic
keywords would be redundant, so you omit them:In the CIL, the method is marked
virtual
andabstract
.(Note that Java allows interface members to be declared
public abstract
).For the implementing class, there are some options:
Non-overridable: In C# the class doesn't declare the method as
virtual
. That means that it cannot be overridden in a derived class (only hidden). In the CIL the method is still virtual (but sealed) because it must support polymorphism regarding the interface type.Overridable: Both in C# and in the CIL the method is
virtual
. It participates in polymorphic dispatch and it can be overridden.Explicit: This is a way for a class to implement an interface but not provide the interface methods in the public interface of the class itself. In the CIL the method will be
private
(!) but it will still be callable from outside the class from a reference to the corresponding interface type. Explicit implementations are also non-overridable. This is possible because there's a CIL directive (.override
) that will link the private method to the corresponding interface method that it's implementing.[C#]
[CIL]
In VB.NET, you can even alias the interface method name in the implementing class.
[VB.NET]
[CIL]
Now, consider this weird case:
If
Base
andDerived
are declared in the same assembly, the compiler will makeBase::Method
virtual and sealed (in the CIL), even thoughBase
doesn't implement the interface.If
Base
andDerived
are in different assemblies, when compiling theDerived
assembly, the compiler won't change the other assembly, so it will introduce a member inDerived
that will be an explicit implementation forMyInterface::Method
that will just delegate the call toBase::Method
.So you see, every interface method implementation must support polymorphic behavior, and thus must be marked virtual on the CIL, even if the compiler must go through hoops to do it.
此处通过 CSharp 第三版引用来自 CLR 的 Jeffrey Ritcher
Quoting Jeffrey Ritcher from CLR via CSharp 3rd Edition here
是的,就运行时而言,接口实现方法是虚拟的。它是一个实现细节,它使接口工作。虚拟方法在类的 v 表中获取插槽,每个插槽都有一个指向虚拟方法之一的指针。将对象转换为接口类型会生成一个指向实现接口方法的表部分的指针。使用接口引用的客户端代码现在可以看到距离接口指针偏移量为 0 的第一个接口方法指针,等等。
我在原来的答案中低估了 final 属性的重要性。它可以防止派生类重写虚拟方法。派生类必须重新实现接口,其实现方法影子基类方法。这足以实现 C# 语言契约,表明实现方法不是虚拟的。
如果您将示例类中的 Dispose() 方法声明为虚拟方法,您将看到 final 属性被删除。现在允许派生类覆盖它。
Yes, interface implementation methods are virtual as far as the runtime is concerned. It is an implementation detail, it makes interfaces work. Virtual methods get slots in the class' v-table, each slot has a pointer to one of the virtual methods. Casting an object to an interface type generates a pointer to the section of the table that implements the interface methods. The client code that uses the interface reference now sees the first interface method pointer at offset 0 from the interface pointer, etcetera.
What I under-appreciated in my original answer is the significance of the final attribute. It prevents a derived class from overriding the virtual method. A derived class must re-implement the interface, the implementation methods shadow the base class methods. Which is enough to implement the C# language contract that says that the implementation method is not virtual.
If you declare the Dispose() method in the Example class as virtual, you'll see the final attribute getting removed. Now allowing a derived class to override it.
在大多数其他编译代码环境中,接口被实现为 vtable——指向方法体的指针列表。通常,实现多个接口的类将在其内部编译器生成的元数据中的某个位置包含一个接口 vtable 列表,每个接口一个 vtable(以便保留方法顺序)。这也是 COM 接口的典型实现方式。
然而,在 .NET 中,接口并不是作为每个类的不同 vtable 来实现的。接口方法通过所有接口都是其一部分的全局接口方法表进行索引。因此,没有必要将一个方法声明为virtual,以便该方法实现一个接口方法——全局接口方法表可以直接指向该类方法的代码地址。
在其他语言中,甚至在非 CLR 平台中,也不需要声明虚拟方法来实现接口。 Win32 上的 Delphi 语言就是一个例子。
In most other compiled code environments, interfaces are implemented as vtables - a list of pointers to the method bodies. Typically a class that implements multiple interfaces will have somewhere in its internal compiler generated metadata a list of interface vtables, one vtable per interface (so that the method order is preserved). This is how COM interfaces are typically implemented as well.
In .NET, though, interfaces are not implemented as distinct vtables for each class. Interface methods are indexed through a global interface method table that all interfaces are a part of. Therefore, it is not necessary to declare a method virtual in order for that method to implement an interface method - the global interface method table can just point to the code address of the class's method directly.
Declaring a method virtual in order to implement an interface is not required in other languages either, even in non-CLR platforms. The Delphi language on Win32 is one example.
接口是一个比类更抽象的概念,当您声明一个实现接口的类时,您只需说“该类必须具有接口中的这些特定方法,并且无论静态,< em>虚拟、非虚拟、重写,只要具有相同的ID和相同的类型参数”。
支持 Object Pascal(“Delphi”)和 Objective-C(Mac)等接口的其他语言也不要求将接口方法标记为 virtual 和 not virtual。
但是,你可能是对的,我认为在接口中拥有特定的“虚拟”/“覆盖”属性可能是个好主意,以防你想限制实现特定接口的类方法。但是,这也意味着两个接口都有“nonvirtual”、“dontcareifvirtualornot”关键字。
我理解你的问题,因为我在Java中看到类似的东西,当类方法必须使用“@virtual”或“@override”来确保方法是虚拟的时。
Interfaces are a more abstract concept than classes, when you declare a class that implements an interface, you just saying "the class must have these particular methods from the interface, and doesn't matter wheter static, virtual, non virtual, overriden, as long as it have the same I.D. and same type parameters" .
Other languages that support interfaces such Object Pascal ("Delphi") and Objective-C (Mac) doesn't require interface methods to be marked virtual and not virtual, either.
But, you may be right, I think may be a good idea to have a specific "virtual" / "override" attribute in interfaces, in case you want to restrict the classes methods that implement a particular interface. But, that also means to have a "nonvirtual", "dontcareifvirtualornot" keywords, for both interfaces.
I understand your question, because I see something similar in Java, when a class method has to use the "@virtual" or "@override" to be sure that a method is intended to be virtual.
它们不是虚拟的(就我们如何看待它们而言,如果不是就底层实现而言(密封虚拟) - 很高兴在这里阅读其他答案并自己学习一些东西:-)
它们不会覆盖任何东西 - 那里接口中没有实现。
接口所做的只是提供类必须遵守的“契约”——如果您愿意的话,可以是一种模式,以便调用者知道如何调用该对象,即使他们以前从未见过该特定类。
然后由类在契约的范围内实现接口方法——虚拟或“非虚拟”(事实证明是密封的虚拟)。
They aren't virtual (in terms of how we think of them, if not in terms of underlying implementation as (sealed virtual) - good to read the other answers here and learn something myself :-)
They don't override anything - there is no implementation in the interface.
All the interface does is supply a "contract" that the class has to adhere to - a pattern, if you like, so that callers know how to call the object even if they have never seen that particular class before.
It is up to the class to then implement the interface method as it will, within the confines of the contract - virtual or "non-virtual" (sealed virtual as it turns out).