Почему компилятор добавляет дополнительный параметр для делегатов, когда нет закрытия?
Я играл с 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;
Everthing работает как ожидалось:
![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: Я скомпилировал первый код в режимах 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
Ответы
Ответ 1
Хотя это может показаться весьма неожиданным, быстрый поиск показывает, что он по соображениям производительности.
В отчет об ошибке он указал, что делегаты, у которых нет неявных this
, заметно медленнее, чем делегаты, у которых есть неявный this
, потому что делегатам, которые не имеют неявного 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
.
Да, это микро-оптимизация, которая, как правило, не имеет значения, но она доступна всем, в том числе в ситуациях, когда это имеет значение.