问题描述
在C#中使用delegate
关键字时,C#编译器会自动生成一个派生自System.MulticastDelegate
类的类.
When delegate
keyword is used in C#, the C# compiler automatically generates a class derived from System.MulticastDelegate
class.
这个编译器生成的类也包含 3 个方法:Invoke、BeginInvoke 和 EndInvoke
.
This compiler generated class contains 3 methods as well: Invoke, BeginInvoke and EndInvoke
.
所有这三个方法都被标记为 public virtual extern
但有趣的是类本身被标记为 sealed
.
All these three methods are marked public virtual extern
but interestingly the class itself is marked sealed
.
在密封类中定义的虚拟方法不仅违反直觉,而且在 C# 中实际上是非法的.
Virtual methods defined in a sealed class not only strikes as counter-intuitive but are actually illegal in C#.
所以我的问题是,这是否有特定的原因,或者它只是在记住一些假设的未来增强的情况下所做的那些无害的事情之一?
So my question is, is there a specific reason for this or is it just one of those harmless things done keeping in mind some hypothetical future enhancement?
编辑 1:
原因可能是强制使用callVirt"IL 操作码而不是call",以便在尝试执行这三种方法中的任何一种之前,CLR 始终检查委托对象是否为空?虽然我不明白为什么 delegate
在这方面应该是一个特例.
Can the reason be to force use of 'callVirt' IL opcode as opposed to 'call' so that delegate object is always checked for null by the CLR before trying to execute any of the three methods? Though I fail to see why a delegate
should be a special case in this respect.
强制使用 callvirt
也不会对性能造成影响(尽管可能微不足道)
Also isn't it a performance hit to force use of callvirt
(though it may be minuscule)
编辑 2:
添加了 CIL 标记,事实证明 C# 定义委托的方式实际上是由 CIL 标准规定的.标准规定(以下不是全文)
Added CIL tag, as it turns out that the C# way of defining delegates is in fact mandated by the CIL standard. Standard states that (following is not full text)
委托应具有 System.Delegate 的基本类型.代表应被宣布密封,并且代表唯一的成员是此处指定的前两种或所有四种方法.这些方法应声明为运行时和管理.他们不得有主体,因为该主体应由 VES 自动创建.其他委托上可用的方法是从类继承的基类库中的 System.Delegate.委托方法是:
Delegates shall have a base type of System.Delegate. Delegates shall be declared sealed, and the only members a delegate shall have are either the first two or all four methods as specified here. These methods shall be declared runtime and managed. They shall not have a body, since that body shall be created automatically by the VES. Other methods available on delegates are inherited from the class System.Delegate in the Base Class Library. The delegate methods are:
- 实例构造函数
- Invoke 方法应该是virtual
- BeginInvoke 方法(如果存在)应为虚拟
- EndInvoke 方法应该是虚拟的
所以这绝对不是编译器过程的副作用,或者类似于其他有趣的编译器输出.
So this is definitely not a side effect of compiler process or is similar to other interesting compiler outputs.
如果标准强调某事,那一定是有充分的理由和理由.
If standard emphasizes something, it must be for some good reason and rationale.
那么现在的问题是,为什么 CIL 代表标准同时强调密封和虚拟?
So the question now is why does CIL standard for delegates emphasizes on sealed and virtual at the same time?
问题在这里吗?:
他们不应该有身体,因为身体应该由 VES 自动创建.
They shall not have a body, since that body shall be created automatically by the VES.
它们是否被标记为虚拟,以便在调用这些方法时可以执行 VES/CLR 生成的主体?
Are they marked virtual so that the VES/CLR generated body can be executed on invocation of these methods?
推荐答案
正如我在问题中指出的那样,这种密封的虚拟异常实际上是由 CIL 标准规定的.目前尚不清楚为什么 CIL 标准特别提到委托方法 Invoke
、BeginInvoke
和 EndInvoke
应该是虚拟的,同时又强制密封 Delegate
继承类.
As I noted in my question that this sealed virtual anomaly is in fact mandated by CIL standard. It remains unclear why CIL standard specifically mentions that delegate methods Invoke
, BeginInvoke
and EndInvoke
should be virtual while at the same time mandating to seal the Delegate
inherited class.
此外,通过 SSCLI 代码后,我了解到 JIT 编译器的内部优化会自动将密封类的虚拟方法上的任何 callvirt
调用转换为带有额外空值检查的正常调用.这意味着,尽管在 IL 中被标记为虚拟,但在通过 callvirt
指令调用其 Invoke(或任何其他两个)方法时,委托不会受到任何性能影响.
Also, after going through SSCLI code I learnt that internal optimization of JIT compiler automatically translates any callvirt
call on a virtual method of a sealed class to normal call with additional null check. This means that delegates do not suffer any performance hit when its Invoke (or any of the other two) method is called through callvirt
instruction despite being marked virtual in the IL.
当一个委托的调用被调用时,CLR 会自动为这个方法发出一个高度优化的主体,而不是编译 IL 代码来生成它为普通"方法所做的主体.这与在 IL 中被标记为 virtual
无关.
When a delegate's invoke is called, CLR automatically emits a highly optimized body for this method as opposed to compiling IL code to generate body which it does for 'normal' methods. This has nothing to do with being marked virtual
in the IL.
我还通过手动修改 IL 代码并重新组装它验证了 virtual 可以安全地从生成的委托类的 IL 代码中删除.尽管违反了 CIL 标准,但生成的程序集运行良好.
I have also verified by hand modifying IL code and re-assembling it that virtual can be safely removed from the generated delegate class's IL code. The generated assembly despite being in violation of the CIL standard runs perfectly fine.
.class private auto ansi beforefieldinit MainApp
extends [mscorlib]System.Object
{
.class auto ansi sealed nested private Echo
extends [mscorlib]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname
instance void .ctor(object 'object',
native int 'method') runtime managed
{
} // end of method Echo::.ctor
.method public hidebysig instance int32 Invoke(int32 i) runtime managed
{
} // end of method Echo::Invoke
.method public hidebysig instance class [mscorlib]System.IAsyncResult
BeginInvoke(int32 i,
class [mscorlib]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method Echo::BeginInvoke
.method public hidebysig instance int32 EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
} // end of method Echo::EndInvoke
} // end of class Echo
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 34 (0x22)
.maxstack 3
.locals init ([0] class MainApp app,
[1] class MainApp/Echo dele)
IL_0000: nop
IL_0001: newobj instance void MainApp::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldftn instance int32 MainApp::DoEcho(int32)
IL_000e: newobj instance void MainApp/Echo::.ctor(object,
native int)
IL_0013: stloc.1
IL_0014: ldloc.1
IL_0015: ldc.i4.5
//callvirt can also be replaced by call without affecting functionality
// since delegate object is essentially not null here
IL_0016: callvirt instance int32 MainApp/Echo::Invoke(int32)
IL_001b: call void [mscorlib]System.Console::WriteLine(int32)
IL_0020: nop
IL_0021: ret
} // end of method MainApp::Main
.method private hidebysig instance int32
DoEcho(int32 i) cil managed
{
// Code size 7 (0x7)
.maxstack 1
.locals init ([0] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
} // end of method MainApp::DoEcho
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method MainApp::.ctor
} // end of class MainApp
请注意,我已将虚拟方法转换为普通实例方法.
Note that I have converted the virtual methods to normal instance methods.
由于这个更改后的 IL 运行得非常好,它证明了密封委托类中的标准强制虚拟方法不是必需的.它们也可以是普通的实例方法.
Since this changed IL runs perfectly fine it proves that standard mandated virtual methods in the sealed delegate class are not necessary. They can be normal instance methods as well.
所以这个异常很可能是强调调用这三个委托方法实际上会导致调用其他一些方法(即运行时多态性,就像正常"虚拟方法一样),或者这是为了适应未来与代表相关的一些假设增强.
So in all probability this anomaly is either to emphasize that calling these three delegate methods will in-fact result in calling of some other method (i.e. run-time polymorphism just like 'normal' virtual methods) or this has been so to accommodate some future hypothetical enhancement related to delegates.
这篇关于编译器为委托关键字生成的密封类包含虚方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!