Скорее всего, потому что первый вызов кода не был связан.Я решил посмотреть на IL, и они практически идентичны.
Func<int, Foo> func = x => new Foo(x * 2);
Expression<Func<int, Foo>> exp = x => new Foo(x * 2);
var func2 = exp.Compile();
Array.ForEach(func.Method.GetMethodBody().GetILAsByteArray(), b => Console.WriteLine(b));
var mtype = func2.Method.GetType();
var fiOwner = mtype.GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic);
var dynMethod = fiOwner.GetValue(func2.Method) as DynamicMethod;
var ilgen = dynMethod.GetILGenerator();
byte[] il = ilgen.GetType().GetMethod("BakeByteArray", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(ilgen, null) as byte[];
Console.WriteLine("Expression version");
Array.ForEach(il, b => Console.WriteLine(b));
Этот код возвращает нам байтовые массивы и выводит их на консоль.Вот вывод на моей машине: *
2
24
90
115
13
0
0
6
42
Expression version
3
24
90
115
2
0
0
6
42
А вот версия первой функции рефлектора: *
L_0000: ldarg.0
L_0001: ldc.i4.2
L_0002: mul
L_0003: newobj instance void ConsoleApplication7.Foo::.ctor(int32)
L_0008: ret
Во всем методе всего 2 байта!Это первый код операции, который предназначен для первого метода ldarg0 (загрузить первый аргумент), но для второго метода ldarg1 (загрузить второй аргумент).Разница здесь в том, что объект, сгенерированный выражением, на самом деле имеет цель объекта Closure
.Это также может учитывать.
Следующий код операции для обоих: ldc.i4.2 (24), что означает загрузку 2 в стек, следующий код операции для mul
(90), следующий код операцииэто код операции newobj
(115).Следующие 4 байта являются токеном метаданных для объекта .ctor
.Они отличаются, так как эти два метода фактически размещены в разных сборках.Анонимный метод находится в анонимной сборке.К сожалению, я не дошел до того, чтобы выяснить, как разрешить эти токены.Окончательный код операции - 42, который равен ret
.Каждая функция CLI должна заканчиваться ret
даже функциями, которые ничего не возвращают.
Возможностей немного, объект закрытия как-то вызывает замедление работы, что может быть правдой (но маловероятно)джиттер не сработал в методе, и, поскольку вы стреляли с быстрым вращением, у него не было времени для перехода по этому пути, вызывая более медленный путь.Компилятор C # в vs также может генерировать различные соглашения о вызовах, и MethodAttributes
, который может служить подсказкой для джиттера для выполнения различных оптимизаций.
В конечном счете, я даже не буду дистанционно беспокоиться об этой разнице.Если вы действительно вызываете свою функцию 3 миллиарда раз в ходе вашего приложения, и получаемая разница составляет 5 полных секунд, вы, вероятно, будете в порядке.