为什么编译器在没有闭包时为委托添加额外的参数?

时间:2014-12-31 20:13:17

标签: c# delegates cil

我正在玩delegates并注意到当我创建Func<int,int,int>时,如下例所示:

Func<int, int, int> func1 = (x, y) => x * y;

编译器生成的方法的签名不是我所期望的:

enter image description here

正如您所看到的,它需要一个对象才能获得它的第一个参数。但是当有一个关闭时:

int z = 10;
Func<int, int, int> func1 = (x, y) => x * y * z;

一切都按预期工作:

enter image description here

这是带有额外参数的方法的IL代码:

    .method private hidebysig static int32  '<Main>b__0'(object A_0,
                                                     int32 x,
                                                     int32 y) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       8 (0x8)
  .maxstack  2
  .locals init ([0] int32 V_0)
  IL_0000:  ldarg.1
  IL_0001:  ldarg.2
  IL_0002:  mul
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method Program::'<Main>b__0'

似乎甚至没有使用参数A_0。那么,第一种情况下object参数的目的是什么?当有关闭时为什么不添加?

注意:如果您对标题有更好的了解,请随时修改。

注2:我在DebugRelease模式下编译了第一个代码,没有区别。但我在Debug模式下编译第二个以获得闭包行为,因为它在Release模式下优化了局部变量。

注3:我正在使用Visual Studio 2014 CTP

修改:这是第一种情况下Main生成的代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       30 (0x1e)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Func`3<int32,int32,int32> func1)
  IL_0000:  nop
  IL_0001:  ldsfld     class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_001c
  IL_0009:  pop
  IL_000a:  ldnull
  IL_000b:  ldftn      int32 ConsoleApplication9.Program::'<Main>b__0'(object,
                                                                       int32,
                                                                       int32)
  IL_0011:  newobj     instance void class [mscorlib]System.Func`3<int32,int32,int32>::.ctor(object,
                                                                                             native int)
  IL_0016:  dup
  IL_0017:  stsfld     class [mscorlib]System.Func`3<int32,int32,int32> ConsoleApplication9.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
  IL_001c:  stloc.0
  IL_001d:  ret
} // end of method Program::Main

1 个答案:

答案 0 :(得分:18)

虽然这看起来非常令人惊讶,但快速搜索表明它出于性能原因。

a bug report about it上,它指出那些没有隐式this的代表比具有隐式this的代表慢得多,因为代表们不要#&# 39;每当调用委托时,都有一个隐含的this需要做一些复杂的参数改组:

假设您致电func1(1, 2)。这看起来像(伪代码,而不是CIL)

push func1
push 1
push 2
call Func<,,>::Invoke

当知道这个func1被绑定到一个静态函数时,需要两个int值,那么它需要执行相当于

的任何一个
push arg.1
push arg.2
call method

arg.0 = arg.1
arg.1 = arg.2
jmp method

当知道func1被绑定到一个取null和两个int值的静态函数时,它只需执行等效的

arg.0 = null
jmp method

因为环境已经完美地设置为输入一个参考类型和两个int值的函数。

是的,这是一项微观优化,通常无关紧要,但它是每个人都能从中受益的,包括那些重要的情况。