c# - usar - ¿Por qué el compilador agrega un parámetro adicional para los delegados cuando no hay cierre?
para qué sirve un delegado c# (1)
Aunque esto puede parecer muy sorprendente, una búsqueda rápida muestra que es por razones de rendimiento.
En un informe de error al respecto , se señala que los delegados que no tienen implícito this
son considerablemente más lentos que los delegados que sí lo tienen implícitamente, porque los delegados que no tienen implícito this
necesitan hacer un poco de complicados argumentos para barajar. Se invoca al delegado:
Supongamos que llamas func1(1, 2)
. Esto parece (pseudo-código, no CIL)
push func1
push 1
push 2
call Func<,,>::Invoke
Cuando se sabe que esta func1
está vinculada a una función estática que toma dos valores int
, debe realizar el equivalente de cualquiera
push arg.1
push arg.2
call method
o
arg.0 = arg.1
arg.1 = arg.2
jmp method
Mientras que se sabe que func1
está vinculado a una función estática que toma valores null
y dos valores int
, solo necesita realizar el equivalente de
arg.0 = null
jmp method
ya que el entorno ya está configurado perfectamente para ingresar a una función tomando un tipo de referencia y dos valores int
.
Sí, es una microoptimización que normalmente no importa, pero es una de la que todos se benefician, incluidos aquellos en situaciones en las que sí importa.
Estaba jugando con delegates
y noté que cuando creo un Func<int,int,int>
como el siguiente ejemplo:
Func<int, int, int> func1 = (x, y) => x * y;
La firma del método generado por el compilador no es lo que esperaba:
Como puedes ver, toma un objeto para su primer parámetro. Pero cuando hay un cierre:
int z = 10;
Func<int, int, int> func1 = (x, y) => x * y * z;
Todo funciona como se espera:
Este es el código IL para el método con parámetro extra:
.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''
Parece que el parámetro A_0
ni siquiera se usa. Entonces, ¿cuál es el propósito del parámetro object
en el primer caso? ¿Por qué no se agrega cuando hay un cierre?
Nota: Si tiene una mejor idea para el título, no dude en editarlo.
Nota 2: Compilé el primer código en los modos Debug
y Release
, no hubo diferencia. Pero compilé el segundo en modo Debug
para obtener un comportamiento de cierre, ya que optimiza la variable local en el modo Release
.
Nota 3: Estoy usando Visual Studio 2014 CTP
.
Editar: Este es el código generado para Main
en el primer caso:
.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