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

时间:2012-07-10 12:33:33

标签: c# .net delegates cil il

在C#中使用delegate关键字时,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.   
  3. Invoke方法应为虚拟
  4.   
  5. BeginInvoke方法(如果存在)应为虚拟
  6.   
  7. EndInvoke方法应为虚拟
  8.   

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

如果标准强调某事,那必须是出于某种充分的理由和理由。

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

捕获物是否在这里?:

  

他们没有身体,因为该身体应由VES自动创建。

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

4 个答案:

答案 0 :(得分:5)

您正在被用于查看类型定义的反汇编程序绊倒。必须将IL转换回可识别的语言,如C#。这通常不能完全保真,IL的规则 与C#语言规则相同。这不仅适用于委托,接口实现方法也是虚拟的,即使您没有在C#代码中声明它是虚拟的。

如果可以通过代码分析确定目标对象,IL实际上允许编译器为虚拟方法发出非虚拟调用。但是,委托或接口调用永远不会发生这种情况。 IL允许对非虚方法进行虚拟调用,这是C#编译器使用gusto来实现保证实例方法永远不能使用null this 调用的保证。

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

答案 1 :(得分:4)

正如我在我的问题中所指出的那样,这种密封的虚拟异常实际上是由CIL标准强制执行的。目前尚不清楚为什么CIL标准特别提到委托方法InvokeBeginInvokeEndInvoke应该是虚拟的,同时要求密封Delegate继承的类。

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

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

我还通过手动修改IL代码并重新组装它来验证可以从生成的委托类的IL代码中安全地删除虚拟代码。尽管违反了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

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

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

所以很可能这个异常要么强调调用这三个委托方法实际上会导致调用其他一些方法(即运行时多态性就像'普通'虚拟方法)或者这样做适应与代表相关的一些未来假设增强。

答案 2 :(得分:3)

这是编译过程的副作用。我不知道确切的原因,这种行为有更多的例子。例如,已编译的静态类将成为一个抽象的密封类(因此您无法创建它的实例,也无法从中继承)。

答案 3 :(得分:1)

似乎没有具体的代表。 我试过这个例子:

 public abstract class Base
    {
        public abstract void Test();
    }

    public sealed class Derived : Base
    {
        public override  void Test()
        {
            throw new NotImplementedException();
        }
    }

在ILDasm中我得到了这个用于Test()的实现:

.method public hidebysig virtual instance void 
        Test() cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  newobj     instance void [mscorlib]System.NotImplementedException::.ctor()
  IL_0006:  throw
} // end of method Derived::Test

可能是override关键字不是CLR关键字。