18

delegateC# 中使用关键字时,C# 编译器会自动生成从 class 派生的System.MulticastDelegate类。

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

所有这三个方法都被标记public virtual extern了,但有趣的是类本身也被标记了sealed

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

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

编辑1:

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

强制使用也不是对性能的影响callvirt(尽管它可能是微不足道的)

编辑2:

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

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

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

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

如果标准强调某事,那一定是有充分理由和理由的。

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

问题在这里吗?:

它们不应有实体,因为该实体应由 VES 自动创建。

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

4

4 回答 4

5

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

为了进一步搅浑水,如果编译器可以从代码分析中确定目标对象,IL 实际上允许编译器发出对虚拟方法的非虚拟调用。但是对于委托或接口调用,这永远不会发生。并且 IL 允许对非虚拟方法进行虚拟调用,这是 C# 编译器使用 gusto 执行的操作,以确保永远不能使用 null this调用实例方法。

但是这种 C# 用法是一个聪明的技巧,只有在设计 CLR 之后才发现。virtual的初衷当然是要注解该方法应该用 Callvirt 调用。最终这并不重要,因为编译器知道委托和接口行为,并且总是会发出 Callvirt。实际的方法调用是在假定 Callvirt 激活的 CLR 代码中实现的。

于 2012-07-10T14:01:33.397 回答
4

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

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

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

我还通过手动修改 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 运行得非常好,因此证明密封委托类中的标准强制虚拟方法不是必需的。它们也可以是普通的实例方法。

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

于 2012-07-12T11:40:01.227 回答
3

这是编译过程的副作用。我不知道确切的原因,这种行为还有更多的例子。例如,一个编译的静态类变成了一个抽象的密封类(所以你不能创建它的实例,也不能从它继承)。

于 2012-07-10T12:36:21.253 回答
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 关键字。

于 2012-07-10T12:44:08.613 回答