The performance difference is caused by the different performance of Invoke()
(fast) and DynamicInvoke()
(slow). When taking a look at the generated IL of a direct call to a Func<object>
typed delegate you can see that the resulting IL will actually call the Invoke()
method:
static void TestInvoke(Func<object> func) {
func();
}
The above compiles to IL code looking something like this (in a debug build):
.method private hidebysig static void TestInvoke(class [mscorlib]System.Func`1<object> func) cil managed {
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0 // func
IL_0002: callvirt instance !0/*object*/ class [mscorlib]System.Func`1<object>::Invoke()
IL_0007: pop
IL_0008: ret
} // end of method Program::TestInvoke
And the Invoke()
method is much faster than the DynamicInvoke()
method, since it basically doesn't need to resolve the type of the delegate (as it is already known). The following answer to another question explains the difference of Invoke()
and DynamicInvoke()
in a bit more detail:
https://stackoverflow.com/a/12858434/6122062
The following very simplified and probably not very accurate test shows a huge difference in perfomance. As you can see there I'm even using the same delegate, just calling it in different ways:
class Program {
static void Main(string[] args) {
var ex = Expression.Lambda<Func<object>>(Expression.New(typeof(object))).Compile();
Stopwatch timer = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++) TestInvoke(ex);
Console.WriteLine($"Invoke():\t\t{timer.Elapsed.ToString()}");
timer = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++) TestDynamicInvoke(ex);
Console.WriteLine($"DynamicInvoke():\t{timer.Elapsed.ToString()}");
Console.ReadKey(true);
}
static void TestInvoke(Func<object> func) {
func();
}
static void TestDynamicInvoke(Delegate deleg) {
deleg.DynamicInvoke();
}
}
Results on my PC at home using a release build, without an attached debugger (as mentioned above I know this simple test might not be very accuarate, but it demonstates the huge difference in performance)
Invoke(): 00:00:00.0080935
DynamicInvoke(): 00:00:00.8382236