【问题标题】:Are Delegates more lightweight than classes?代表比类更轻量级吗?
【发布时间】:2011-02-19 17:36:58
【问题描述】:

我尝试反汇编 C# 创建的可执行文件,但无法得出结论。我想知道的是,对于 CLR,c# 的委托是真的特殊实体还是只是编译器糖?

我问这个是因为我正在实现一种可编译为 C# 的语言,而且将匿名函数编译为类比编译为委托对我来说更有趣。但是我不想使用以后会后悔的设计,因为它们可能会占用更多内存(我认为 Java 的 PermGen 可以作为我提问的基础。尽管我知道 CLR 没有这样的东西)。

谢谢!

--编辑

为了更清楚一点,我想知道两者之间是否存在(以及有什么区别):

void Main()
{
    Func<int, int, int> add = delegate(int a, int b) {return a + b;};
}

例如

class AnonFuncion__1219023 : Fun3
{
    public override int invoke(int a, int b)
    {
        return a + b;
    }
}

--编辑

我认为以下之间可能存在很大差异:

class Program
{
    static int add(int a, int b)
    {
        return a + b;
    }

    static void Main()
    {
        Func<int, int, int> add = Program.add;
    }
}

class Function__432892 : Fun3
{
    public override int invoke(int a, int b)
    {
        return Program.add(a, b);
    }
}

不过,我在某处读到语法Func&lt;int, int, int&gt; add = Program.add; 只是Func&lt;int, int, int&gt; add = delegate(int a, int b) { return Program.add; }; 的糖。但我真的不知道这是不是真的。 我还可以看到 C# 编译器已经缓存了所有这些实例,因此它们只构造一次。不过,我可以用我的编译器做同样的事情。

【问题讨论】:

    标签: c# delegates clr internals il


    【解决方案1】:

    我很惊讶您无法通过反汇编可执行文件得出结论。让我们看一个非常简单的东西:

            using System;
    
            class A
            {
              int _x;
    
              public A(int x)
              {
                _x = x;
              }
    
              public void Print(int y)
              {
                Console.WriteLine(_x + y);
              }
            }
    
            interface IPseudoDelegateVoidInt
            {
              void Call(int y);
            }
    
    
            class PseudoDelegateAPrint : IPseudoDelegateVoidInt
            {
              A _target;
              public PseudoDelegateAPrint(A target)
              {
                _target = target;
              }
    
              public void Call(int y)
              {
                _target.Print(y);
              }
            }
    
    
    
            class Program
            {
              delegate void RealVoidIntDelegate(int x);
              static void Main()
              {
                A a = new A(5);
                IPseudoDelegateVoidInt pdelegate = new PseudoDelegateAPrint(a);
                RealVoidIntDelegate rdelegate = new RealVoidIntDelegate(a.Print);
                pdelegate.Call(2);
                rdelegate(2);
              }
            }
    

    如果我们拆开它,我们会得到

            //  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.1
            //  Copyright (c) Microsoft Corporation.  All rights reserved.
    
    
    
            // Metadata version: v4.0.30319
            .assembly extern mscorlib
            {
              .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
              .ver 4:0:0:0
            }
            .assembly del
            {
              .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
              .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                                         63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
              .hash algorithm 0x00008004
              .ver 0:0:0:0
            }
            .module del.exe
            // MVID: {87A2A843-A5F2-4D40-A96D-9940579DE26E}
            .imagebase 0x00400000
            .file alignment 0x00000200
            .stackreserve 0x00100000
            .subsystem 0x0003       // WINDOWS_CUI
            .corflags 0x00000001    //  ILONLY
            // Image base: 0x0000000000B60000
    
    
            // =============== CLASS MEMBERS DECLARATION ===================
    
            .class private auto ansi beforefieldinit A
                   extends [mscorlib]System.Object
            {
              .field private int32 _x
              .method public hidebysig specialname rtspecialname 
                      instance void  .ctor(int32 x) cil managed
              {
                // Code size       17 (0x11)
                .maxstack  8
                IL_0000:  ldarg.0
                IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
                IL_0006:  nop
                IL_0007:  nop
                IL_0008:  ldarg.0
                IL_0009:  ldarg.1
                IL_000a:  stfld      int32 A::_x
                IL_000f:  nop
                IL_0010:  ret
              } // end of method A::.ctor
    
              .method public hidebysig instance void 
                      Print(int32 y) cil managed
              {
                // Code size       16 (0x10)
                .maxstack  8
                IL_0000:  nop
                IL_0001:  ldarg.0
                IL_0002:  ldfld      int32 A::_x
                IL_0007:  ldarg.1
                IL_0008:  add
                IL_0009:  call       void [mscorlib]System.Console::WriteLine(int32)
                IL_000e:  nop
                IL_000f:  ret
              } // end of method A::Print
    
            } // end of class A
    
            .class interface private abstract auto ansi IPseudoDelegateVoidInt
            {
              .method public hidebysig newslot abstract virtual 
                      instance void  Call(int32 y) cil managed
              {
              } // end of method IPseudoDelegateVoidInt::Call
    
            } // end of class IPseudoDelegateVoidInt
    
            .class private auto ansi beforefieldinit PseudoDelegateAPrint
                   extends [mscorlib]System.Object
                   implements IPseudoDelegateVoidInt
            {
              .field private class A _target
              .method public hidebysig specialname rtspecialname 
                      instance void  .ctor(class A target) cil managed
              {
                // Code size       17 (0x11)
                .maxstack  8
                IL_0000:  ldarg.0
                IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
                IL_0006:  nop
                IL_0007:  nop
                IL_0008:  ldarg.0
                IL_0009:  ldarg.1
                IL_000a:  stfld      class A PseudoDelegateAPrint::_target
                IL_000f:  nop
                IL_0010:  ret
              } // end of method PseudoDelegateAPrint::.ctor
    
              .method public hidebysig newslot virtual final 
                      instance void  Call(int32 y) cil managed
              {
                // Code size       15 (0xf)
                .maxstack  8
                IL_0000:  nop
                IL_0001:  ldarg.0
                IL_0002:  ldfld      class A PseudoDelegateAPrint::_target
                IL_0007:  ldarg.1
                IL_0008:  callvirt   instance void A::Print(int32)
                IL_000d:  nop
                IL_000e:  ret
              } // end of method PseudoDelegateAPrint::Call
    
            } // end of class PseudoDelegateAPrint
    
            .class private auto ansi beforefieldinit Program
                   extends [mscorlib]System.Object
            {
              .class auto ansi sealed nested private RealVoidIntDelegate
                     extends [mscorlib]System.MulticastDelegate
              {
                .method public hidebysig specialname rtspecialname 
                        instance void  .ctor(object 'object',
                                             native int 'method') runtime managed
                {
                } // end of method RealVoidIntDelegate::.ctor
    
                .method public hidebysig newslot virtual 
                        instance void  Invoke(int32 x) runtime managed
                {
                } // end of method RealVoidIntDelegate::Invoke
    
                .method public hidebysig newslot virtual 
                        instance class [mscorlib]System.IAsyncResult 
                        BeginInvoke(int32 x,
                                    class [mscorlib]System.AsyncCallback callback,
                                    object 'object') runtime managed
                {
                } // end of method RealVoidIntDelegate::BeginInvoke
    
                .method public hidebysig newslot virtual 
                        instance void  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
                {
                } // end of method RealVoidIntDelegate::EndInvoke
    
              } // end of class RealVoidIntDelegate
    
              .method private hidebysig static void  Main() cil managed
              {
                .entrypoint
                // Code size       45 (0x2d)
                .maxstack  3
                .locals init (class A V_0,
                         class IPseudoDelegateVoidInt V_1,
                         class Program/RealVoidIntDelegate V_2)
                IL_0000:  nop
                IL_0001:  ldc.i4.5
                IL_0002:  newobj     instance void A::.ctor(int32)
                IL_0007:  stloc.0
                IL_0008:  ldloc.0
                IL_0009:  newobj     instance void PseudoDelegateAPrint::.ctor(class A)
                IL_000e:  stloc.1
                IL_000f:  ldloc.0
                IL_0010:  ldftn      instance void A::Print(int32)
                IL_0016:  newobj     instance void Program/RealVoidIntDelegate::.ctor(object,
                                                                                      native int)
                IL_001b:  stloc.2
                IL_001c:  ldloc.1
                IL_001d:  ldc.i4.2
                IL_001e:  callvirt   instance void IPseudoDelegateVoidInt::Call(int32)
                IL_0023:  nop
                IL_0024:  ldloc.2
                IL_0025:  ldc.i4.2
                IL_0026:  callvirt   instance void Program/RealVoidIntDelegate::Invoke(int32)
                IL_002b:  nop
                IL_002c:  ret
              } // end of method Program::Main
    
              .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 Program::.ctor
    
            } // end of class Program
    
    
            // =============================================================
    
            // *********** DISASSEMBLY COMPLETE ***********************
            // WARNING: Created Win32 resource file C:\Users\logan\del.res
    

    正如您所见,RealVoidIntDelegate 变成了“只是”另一个类。他们将其称为Invoke 而不是Call,并且他们没有使用接口但没有涉及特殊说明,这是相同的基本思想。

    为了详细说明“轻量级”的概念,委托并不比类更轻,因为给定的委托一个类。尤其是在函数字面量语法的情况下,它们真的不能轻得多。但是,对于“使用这些 args 在此对象上调用此方法”的情况,在编译为 C# 时,调用和创建给定委托的重量可能会比自制委托更轻。

    【讨论】:

    • 是的,我承认我是 IL 新手。我尝试使用 ILDasm,它给了我这样的输出: //000043: IntOper add1 = delegate(int x, int y) .​​line 48,54 : 13,15 '' //000044: { //000045: return x + 是; //000046: };
    • 但是我确定我检查了错误的反汇编选项; ) 无论如何,感谢您的详细回答!
    • 但是看着你的反汇编,它引起了我的注意 ldftn 那里。似乎不是为每个匿名函数创建一个类,而只是为委托类型声明
    • 对。这就是加糖的地方。在 C++/CLI 中,您实际上会说gcnew RealVoidintDelegate(a, &amp;A::Print),这可能会使它更清晰一些。您可以使用反射 API 在 C# 中实现类似的功能,但这就是我创建接口的原因。每个类/方法组合都会产生一个新的具体类。在这种情况下,只有一个。真正的 C# 委托有一个对象成员和(相当于)一个 Method 成员(它实际上是一个插槽索引)。
    • 我不知道它是否“只是在这种情况下”,也许编译器足够聪明,可以识别像delegate { a() } 这样的代码并优化额外的类,但在这种情况下是肯定的(至少假设你不计算 DelegateMulticastDelagate 中的代码,甚至可能是这样)。
    【解决方案2】:

    代表是类。 .NET 编译器为每个委托创建一个类型,并且代码必须实例化一个委托。

    在任何情况下,性能差异都可以忽略不计,除非您正在编写非常罕见的应用程序之一,其中每一纳秒都很重要。

    【讨论】:

      【解决方案3】:

      这里没有什么大秘密。引导 Jon Skeet...

      如果您查看C# Specification 的 6.5.3,您将看到编译器如何处理匿名委托的几个示例。小结:

      IF匿名方法不捕获外部变量,
      THEN可以将其创建为封闭类型的静态方法。

      IF 匿名方法引用封闭类型的成员,例如this.x
      那么可以将其创建为封闭类型的实例方法。

      IF 匿名方法捕获一个局部变量,
      THEN 它可以创建为封闭类型内的嵌套类型,实例变量与捕获的匹配变量,以及与委托类型匹配的实例方法。

      最后一个例子更复杂,只是更容易看代码。看看你自己,我认为它会消除所有的猜测。

      【讨论】:

        【解决方案4】:

        两者之间似乎几乎没有区别,顶部的 sn-p 有效地编译成与底部 sn-p 中的几乎相同的东西。

        【讨论】:

          【解决方案5】:

          除了元数据之外,真的没有类这样的东西,一旦一切都得到 JIT 编译。对象的运行时表示是一个大字节数组 足以存储类的所有字段和 它是基类,加上一个存储 哈希码、数字类型标识符和指向共享对象的指针 包含虚函数覆盖地址的 v 表。

          委托是一个对象,其类派生自 System.Delegate。它存储对象和函数指针对的数组。

          匿名函数是没有名字的函数。然而,他们也是 通常与一个称为闭包的对象相关联,该对象包含在包含方法中定义的所有参数和局部变量,该方法创建了指向匿名函数的“匿名委托”。 (实际上它通常只包含那些实际访问的变量)。

          在任何情况下,将堆栈变量移动到堆中可以让匿名委托超越它定义的堆栈帧。

          将匿名函数与其闭包关联的最简单方法是使其成为编译器生成的闭包类的方法。

          所以,如果你有词法闭包,你必须(至少在某些情况下)使用一个类来实现匿名函数。

          如果您没有词法闭包,那么您可以将“匿名函数”作为 带有编译器生成名称的常规函数​​,位于声明它的方法旁边。在语言支持词法闭包但不需要的情况下,这也是一种有用的优化。

          一般来说,如果没有闭包支持,匿名函数就没有用处,所以如果不支持闭包,我不会费心将它们包含在您的语言中。

          【讨论】:

          • 但是无论如何你可以自己定义一个闭包作为一个类,只需将捕获的变量定义为这个生成类的成员类变量。
          • 我也可以随时将变量定义为长度为 1 的数组,以便模拟在堆中定义它们(虽然我不知道它会如何执行)
          • 我的意思是你别无选择。在一般情况下,您必须在编译匿名函数时发出一个类。
          • 如果您是手动编写闭包,则不需要匿名函数。您可以在您创建的手写闭包中为此使用命名函数。
          猜你喜欢
          • 2019-10-04
          • 2015-09-22
          • 2011-06-16
          • 1970-01-01
          • 2011-01-31
          • 1970-01-01
          • 1970-01-01
          • 2020-07-16
          • 2017-01-18
          相关资源
          最近更新 更多