编译器为delegate关键字生成的密封类包含虚拟方法

Ami*_*tal 18 .net c# il delegates cil

delegate关键字在C#所使用的,C#编译器自动生成从派生的类System.MulticastDelegate的类.

这个编译器生成的类也包含3个方法:Invoke, BeginInvoke and EndInvoke.

所有这三种方法都有标记,public virtual extern但有趣的是,类本身已被标记sealed.

在密封类中定义的虚拟方法不仅违反直觉,而且在C#中实际上是非法的.

所以我的问题是,是否有一个特定的原因,或者它只是其中一个无害的事情,记住一些假设的未来增强?

编辑1:

可能的原因是强制使用'callVirt'IL操作码而不是'call',以便在尝试执行这三种方法中的任何一种之前,CLR始终检查委托对象为空?虽然我不明白为什么delegate在这方面应该是一个特例.

也不是强制使用的性能打击callvirt(虽然它可能是微不足道的)

编辑2:

添加了CIL标记,因为事实证明定义委托的C#方式实际上是由CIL标准强制执行的.标准规定(以下不是全文)

代理应具有System.Delegate的基本类型.代表应宣布密封,代表的唯一成员应为此处规定的前两种或全部四种方法.这些方法应声明为运行时和管理.它们不应具有身体,因为该身体应由VES自动创建.委托上可用的其他方法继承自基类库中的System.Delegate类.委托方法是:

  1. 实例构造函数
  2. Invoke方法应该是虚拟的
  3. BeginInvoke方法(如果存在)应为虚拟方法
  4. EndInvoke方法应该是虚拟的

所以这绝对不是编译器进程的副作用,或者类似于其他有趣的编译器输出.

如果标准强调某些东西,那必须是出于某种充分的理由和理由.

所以现在的问题是为什么代表们的CIL标准同时强调密封和虚拟?

渔获物在这里吗?:

它们不应具有身体,因为该身体应由VES自动创建.

它们是否标记为虚拟,以便在调用这些方法时可以执行VES/CLR生成的主体?

Han*_*ant 5

你曾经被看过类型定义的反汇编程序绊倒了.必须将IL转换回可识别的语言,如C#.这不是一般的可能与全保真做,对于IL规则是一样的C#语言规则.这不仅适用于委托,接口实现方法也是虚拟的,即使您没有在C#代码中声明它是虚拟的.

为了进一步混淆水域,IL实际上允许编译器为虚拟方法发出非虚拟调用,如果它可以通过代码分析确定目标对象.但是,委托或接口调用永远不会发生这种情况.和IL允许进行虚拟呼叫到一个非虚方法,一些C#编译器津津有味确实落实保证实例方法不能使用空调用.

但是C#的使用是一个聪明的伎俩,只有在CLR设计完成后才能发现.虚拟的初衷当然是注释应该使用Callvirt调用该方法.最终它并不重要,因为编译器知道委托和接口行为,并将始终发出Callvirt.并且实际的方法调用在CLR代码中实现,该代码假定Callvirt激活.


Ami*_*tal 4

正如我在问题中指出的,这种密封的虚拟异常实际上是 CIL 标准规定的。目前还不清楚为什么 CIL 标准特别提到委托方法InvokeBeginInvoke应该EndInvoke是虚拟的,同时又要求密封Delegate继承的类。

另外,在查看 SSCLI 代码后,我了解到 JIT 编译器的内部优化会自动将对callvirt密封类的虚拟方法的任何调用转换为带有额外空检查的正常调用。callvirt这意味着,尽管在 IL 中被标记为虚拟,但当通过指令调用其 Invoke(或任何其他两个)方法时,委托不会遭受任何性能影响。

当调用委托的调用时,CLR 会自动为此方法发出高度优化的主体,而不是像为“普通”方法那样编译 IL 代码来生成主体。virtual这与IL 中的标记无关。

我还通过手动修改 IL 代码并重新组装它来验证,可以安全地从生成的委托类的 IL 代码中删除 virtual。尽管违反了 CIL 标准,生成的程序集仍然运行得很好。

.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
Run Code Online (Sandbox Code Playgroud)

请注意,我已将虚拟方法转换为普通实例方法。

由于此更改的 IL 运行得非常好,因此证明密封委托类中的标准强制虚拟方法不是必需的。它们也可以是普通的实例方法。

因此,这种异常很可能要么是强调调用这三个委托方法实际上会导致调用某些其他方法(即运行时多态性,就像“正常”虚拟方法一样),要么是为了适应未来的某些方法与代表相关的假设增强。