Being able to dynamically call an arbitrary method on an object or the ability to get/set any arbitrary property by name at run time is very important when it comes to do some generic stuff. In .NET Framework 1.x the only way to do that was using reflection and that means to do it slow. For simple things or if we don’t have a lot of concurrent users we can live with it, but sometimes the performance is important and reflection isn’t an acceptable solution.
Fortunately, the .NET Framework 2.0 introduces a new class that let us to quickly call an arbitrary method without the overhead of a reflection call. How? Well, the idea is to generate the same MSIL as the compiler generates when calling a function but on the fly, and then we obtain a delegate that when called will execute the MSIL we have just created. This is really useful in a lot of scenarios!
If you want to play with this you should download an excellent debugger visualizer that will let you show the MSIL generated by a DynamicMethod:
I have made a little helper class (based on the codeproject article) that can be used to replace MethodInfo.Invoke, PropertyInfo.GetValue, PropertyInfo.SetValue and Activator.CreateInstance that are the reflection methods that I use most often. In order to use it in your own projects, remember to cache the delegates obtained because every time you call the CreateDelegate the MSIL generation takes place and that takes some time.
I have also created a simple test to show a comparison between direct calls, reflection calls and dynamic method calls. The results are here:
The code used for the tests and the helper class for the DynamicMethods are shown here and can be downloaded at the end of this post:
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace Manu.Utils
{
/// <summary>Delegate for calling a method that is not known at runtime.</summary>
/// <param name="target">the object to be called or null if the call is to a static method.</param>
/// <param name="paramters">the parameters to the method.</param>
/// <returns>the return value for the method or null if it doesn't return anything.</returns>
public delegate object FastInvokeHandler(object target, object[] parameters);
/// <summary>Delegate for creating and object at runtime using the default constructor.</summary>
/// <returns>the newly created object.</returns>
public delegate object FastCreateInstanceHandler();
/// <summary>Delegate to get an arbitraty property at runtime.</summary>
/// <param name="target">the object instance whose property will be obtained.</param>
/// <returns>the property value.</returns>
public delegate object FastPropertyGetHandler(object target);
/// <summary>Delegate to set an arbitrary property at runtime.</summary>
/// <param name="target">the object instance whose property will be modified.</param>
/// <param name="parameter"></param>
public delegate void FastPropertySetHandler(object target, object parameter);
/// <summary>Class with helper methods for dynamic invocation generating IL on the fly.</summary>
public static class DynamicCalls
{
public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
{
// generates a dynamic method to generate a FastInvokeHandler delegate
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
ParameterInfo[] parameters = methodInfo.GetParameters();
Type[] paramTypes = new Type[parameters.Length];
// copies the parameter types to an array
for (int i = 0; i < paramTypes.Length; i++) {
if (parameters[i].ParameterType.IsByRef)
paramTypes[i] = parameters[i].ParameterType.GetElementType();
else
paramTypes[i] = parameters[i].ParameterType;
}
LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
// generates a local variable for each parameter
for (int i = 0; i < paramTypes.Length; i++) {
locals[i] = ilGenerator.DeclareLocal(paramTypes[i], true);
}
// creates code to copy the parameters to the local variables
for (int i = 0; i < paramTypes.Length; i++) {
ilGenerator.Emit(OpCodes.Ldarg_1);
EmitFastInt(ilGenerator, i);
ilGenerator.Emit(OpCodes.Ldelem_Ref);
EmitCastToReference(ilGenerator, paramTypes[i]);
ilGenerator.Emit(OpCodes.Stloc, locals[i]);
}
if (!methodInfo.IsStatic) {
// loads the object into the stack
ilGenerator.Emit(OpCodes.Ldarg_0);
}
// loads the parameters copied to the local variables into the stack
for (int i = 0; i < paramTypes.Length; i++) {
if (parameters[i].ParameterType.IsByRef)
ilGenerator.Emit(OpCodes.Ldloca_S, locals[i]);
else
ilGenerator.Emit(OpCodes.Ldloc, locals[i]);
}
// calls the method
if (!methodInfo.IsStatic) {
ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);
} else {
ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
}
// creates code for handling the return value
if (methodInfo.ReturnType == typeof(void)) {
ilGenerator.Emit(OpCodes.Ldnull);
} else {
EmitBoxIfNeeded(ilGenerator, methodInfo.ReturnType);
}
// iterates through the parameters updating the parameters passed by ref
for (int i = 0; i < paramTypes.Length; i++) {
if (parameters[i].ParameterType.IsByRef) {
ilGenerator.Emit(OpCodes.Ldarg_1);
EmitFastInt(ilGenerator, i);
ilGenerator.Emit(OpCodes.Ldloc, locals[i]);
if (locals[i].LocalType.IsValueType)
ilGenerator.Emit(OpCodes.Box, locals[i].LocalType);
ilGenerator.Emit(OpCodes.Stelem_Ref);
}
}
// returns the value to the caller
ilGenerator.Emit(OpCodes.Ret);
// converts the DynamicMethod to a FastInvokeHandler delegate to call to the method
FastInvokeHandler invoker = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
return invoker;
}
/// <summary>Gets the instance creator delegate that can be use to create instances of the specified type.</summary>
/// <param name="type">The type of the objects we want to create.</param>
/// <returns>A delegate that can be used to create the objects.</returns>
public static FastCreateInstanceHandler GetInstanceCreator(Type type)
{
// generates a dynamic method to generate a FastCreateInstanceHandler delegate
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, type, new Type[0], typeof(DynamicCalls).Module);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
// generates code to create a new object of the specified type using the default constructor
ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
// returns the value to the caller
ilGenerator.Emit(OpCodes.Ret);
// converts the DynamicMethod to a FastCreateInstanceHandler delegate to create the object
FastCreateInstanceHandler creator = (FastCreateInstanceHandler)dynamicMethod.CreateDelegate(typeof(FastCreateInstanceHandler));
return creator;
}
public static FastPropertyGetHandler GetPropertyGetter(PropertyInfo propInfo)
{
// generates a dynamic method to generate a FastPropertyGetHandler delegate
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object) }, propInfo.DeclaringType.Module);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
// loads the object into the stack
ilGenerator.Emit(OpCodes.Ldarg_0);
// calls the getter
ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetGetMethod(), null);
// creates code for handling the return value
EmitBoxIfNeeded(ilGenerator, propInfo.PropertyType);
// returns the value to the caller
ilGenerator.Emit(OpCodes.Ret);
// converts the DynamicMethod to a FastPropertyGetHandler delegate to get the property
FastPropertyGetHandler getter = (FastPropertyGetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertyGetHandler));
return getter;
}
public static FastPropertySetHandler GetPropertySetter(PropertyInfo propInfo)
{
// generates a dynamic method to generate a FastPropertySetHandler delegate
DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, null, new Type[] { typeof(object), typeof(object) }, propInfo.DeclaringType.Module);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
// loads the object into the stack
ilGenerator.Emit(OpCodes.Ldarg_0);
// loads the parameter from the stack
ilGenerator.Emit(OpCodes.Ldarg_1);
// cast to the proper type (unboxing if needed)
EmitCastToReference(ilGenerator, propInfo.PropertyType);
// calls the setter
ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetSetMethod(), null);
// terminates the call
ilGenerator.Emit(OpCodes.Ret);
// converts the DynamicMethod to a FastPropertyGetHandler delegate to get the property
FastPropertySetHandler setter = (FastPropertySetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertySetHandler));
return setter;
}
/// <summary>Emits the cast to a reference, unboxing if needed.</summary>
/// <param name="il">The MSIL generator.</param>
/// <param name="type">The type to cast.</param>
private static void EmitCastToReference(ILGenerator ilGenerator, System.Type type)
{
if (type.IsValueType) {
ilGenerator.Emit(OpCodes.Unbox_Any, type);
} else {
ilGenerator.Emit(OpCodes.Castclass, type);
}
}
/// <summary>Boxes a type if needed.</summary>
/// <param name="ilGenerator">The MSIL generator.</param>
/// <param name="type">The type.</param>
private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type type)
{
if (type.IsValueType) {
ilGenerator.Emit(OpCodes.Box, type);
}
}
/// <summary>Emits code to save an integer to the evaluation stack.</summary>
/// <param name="ilGeneartor">The MSIL generator.</param>
/// <param name="value">The value to push.</param>
private static void EmitFastInt(ILGenerator ilGenerator, int value)
{
// for small integers, emit the proper opcode
switch (value) {
case -1:
ilGenerator.Emit(OpCodes.Ldc_I4_M1);
return;
case 0:
ilGenerator.Emit(OpCodes.Ldc_I4_0);
return;
case 1:
ilGenerator.Emit(OpCodes.Ldc_I4_1);
return;
case 2:
ilGenerator.Emit(OpCodes.Ldc_I4_2);
return;
case 3:
ilGenerator.Emit(OpCodes.Ldc_I4_3);
return;
case 4:
ilGenerator.Emit(OpCodes.Ldc_I4_4);
return;
case 5:
ilGenerator.Emit(OpCodes.Ldc_I4_5);
return;
case 6:
ilGenerator.Emit(OpCodes.Ldc_I4_6);
return;
case 7:
ilGenerator.Emit(OpCodes.Ldc_I4_7);
return;
case 8:
ilGenerator.Emit(OpCodes.Ldc_I4_8);
return;
}
// for bigger values emit the short or long opcode
if (value > -129 && value < 128) {
ilGenerator.Emit(OpCodes.Ldc_I4_S, (SByte)value);
} else {
ilGenerator.Emit(OpCodes.Ldc_I4, value);
}
}
}
}
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Diagnostics;
using Manu.Utils;
namespace DynamicMethods
{
public class Program
{
public static void Main(string[] args)
{
// object creation
Console.WriteLine("------ Object Creation ------");
EvaluateMethod("direct - object creation", delegate() {
TestClass p = new TestClass();
}, 1000000);
FastCreateInstanceHandler creator = DynamicCalls.GetInstanceCreator(typeof(TestClass));
EvaluateMethod("dynamic method - object creation", delegate() {
TestClass p = (TestClass)creator.Invoke();
}, 1000000);
EvaluateMethod("reflection - object creation", delegate() {
TestClass p = (TestClass)Activator.CreateInstance(typeof(TestClass));
}, 1000000);
// property get
Console.WriteLine("------ Property Get ------");
TestClass prod = new TestClass();
prod.Num = 123;
EvaluateMethod("direct - property get", delegate() {
int num = prod.Num;
}, 1000000);
PropertyInfo propInfo = typeof(TestClass).GetProperty("Num");
FastPropertyGetHandler getter = DynamicCalls.GetPropertyGetter(propInfo);
EvaluateMethod("dynamic method - property get", delegate() {
int num = (int)getter(prod);
}, 1000000);
EvaluateMethod("reflection - property get", delegate() {
int num = (int)propInfo.GetValue(prod, null);
}, 1000000);
// property set
Console.WriteLine("------ Property Set ------");
EvaluateMethod("direct - property set", delegate() {
prod.Num = 33;
}, 1000000);
FastPropertySetHandler setter = DynamicCalls.GetPropertySetter(propInfo);
EvaluateMethod("dynamic method - property set", delegate() {
setter(prod, 32);
}, 1000000);
EvaluateMethod("reflection - property set", delegate() {
propInfo.SetValue(prod, 31, null);
}, 1000000);
// instance method call
Console.WriteLine("------ Instance Method Call ------");
EvaluateMethod("direct - instance method call", delegate() {
int result = prod.Multiply(3, 5);
}, 1000000);
MethodInfo methodInfo1 = typeof(TestClass).GetMethod("Multiply");
FastInvokeHandler fastInvoker1 = DynamicCalls.GetMethodInvoker(methodInfo1);
EvaluateMethod("dynamic method - instance method call", delegate() {
int result = (int)fastInvoker1(prod, new object[] { 3, 5 });
}, 1000000);
EvaluateMethod("reflection - instance method call", delegate() {
int result = (int)methodInfo1.Invoke(prod, new object[] { 3, 5 });
}, 1000000);
// static method call
Console.WriteLine("------ Static Method Call ------");
EvaluateMethod("direct - static method call", delegate() {
int result = TestClass.StaticMultiply(3, 5);
}, 1000000);
MethodInfo methodInfo2 = typeof(TestClass).GetMethod("StaticMultiply");
FastInvokeHandler fastInvoker2 = DynamicCalls.GetMethodInvoker(methodInfo2);
EvaluateMethod("dynamic method - static method call", delegate() {
int result = (int)fastInvoker2(null, new object[] { 3, 5 });
}, 1000000);
EvaluateMethod("reflection - static method call", delegate() {
int result = (int)methodInfo2.Invoke(null, new object[] { 3, 5 });
}, 1000000);
Console.ReadLine();
}
delegate void MethodToEvaluate();
// evaluates a method the specified number of times and print performance information
static void EvaluateMethod(string testName, MethodToEvaluate method, long times)
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (long i = 0; i < times; i++) {
method();
}
watch.Stop();
Console.WriteLine(testName + ": " + watch.ElapsedMilliseconds + "ms");
}
}
}