我正在玩delegates
并注意到当我创建Func<int,int,int>
时,如下例所示:
Func<int, int, int> func1 = (x, y) => x * y;
编译器生成的方法的签名不是我所期望的:
正如您所看到的,它需要一个对象才能获得它的第一个参数。但是当有一个关闭时:
int z = 10;
Func<int, int, int> func1 = (x, y) => x * y * z;
一切都按预期工作:
这是带有额外参数的方法的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:我在Debug
和Release
模式下编译了第一个代码,没有区别。但我在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
答案 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
值的函数。
是的,这是一项微观优化,通常无关紧要,但它是每个人都能从中受益的,包括那些重要的情况。