As you all know, the performance of invoking a method using reflection is not best. Or as I would say – it’s beneath contempt.
Due to a performance problem that I had the last days I had to dig into the problem and found a way to improve the method invocation at about 2000%. A MSDN article offered by my collegue Alexander Jung gave me the idea.
The idea behind is to create a generic DynamicMethod at runtime to set and get the value of a property. The dynamic methods will be invoked trough a generic delegate that can be created for that dynamic methods at runtime. The delegate looks like:
public delegate void GenericSetter(object target, object value);
public delegate object GenericGetter(object target);
The target parameter defines the instance of an object of that the property will set. The example code for my performance messure program looks like:
/*
* Create a dynamic method and a delegate for it
*/
PropertyInfo testPropertyInfo = typeof(BusinessEntitiy).GetProperty("Counter");
setter = CreateSetMethod(testPropertyInfo);
getter = CreateGetMethod(testPropertyInfo);
/*
* Now repeat it 1 million times and Set/Get the value
*/
DateTime startTime = DateTime.Now;
for (int x = 0; x < REPEATS; x++)
{
setter(be, x);
int check = (int)getter(be);
Debug.Assert(check == x, "Setter call failed.");
}
The magic behind that code are the methods CreateSetMethod and CreateGetMethod. The implementation of those methods can be downloaded at the end of that posting.
These methods are using the DynamicMethod class and the ILGenerator in order to create a dynamic method at runtime that calls the getter and setter property without reflection. The performance boost is unbelievable. I tested the raw set and get functionality with about 1 million property calls. The standard reflection method invocation took about 4,6 seconds; the dynamic method call took only about 0,22 seconds on my machine.
If you are interessted in that solution you can download the complete example using the following link:
2000% faster using dynamic method calls
Appendix:
///
/// Creates a dynamic setter for the property
///
private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
{
/*
* If there's no setter return null
*/
MethodInfo setMethod = propertyInfo.GetSetMethod();
if (setMethod == null)
return null;
/*
* Create the dynamic method
*/
Type[] arguments = new Type[2];
arguments[0] = arguments[1] = typeof(object);
DynamicMethod setter = new DynamicMethod(
String.Concat("_Set", propertyInfo.Name, "_"),
typeof(void), arguments, propertyInfo.DeclaringType);
ILGenerator generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
generator.Emit(OpCodes.Ldarg_1);
if (propertyInfo.PropertyType.IsClass)
generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
else
generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
generator.EmitCall(OpCodes.Callvirt, setMethod, null);
generator.Emit(OpCodes.Ret);
/*
* Create the delegate and return it
*/
return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
}
///
/// Creates a dynamic getter for the property
///
private static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
{
/*
* If there's no getter return null
*/
MethodInfo getMethod = propertyInfo.GetGetMethod();
if (getMethod == null)
return null;
/*
* Create the dynamic method
*/
Type[] arguments = new Type[1];
arguments[0] = typeof(object);
DynamicMethod getter = new DynamicMethod(
String.Concat("_Get", propertyInfo.Name, "_"),
typeof(object), arguments, propertyInfo.DeclaringType);
ILGenerator generator = getter.GetILGenerator();
generator.DeclareLocal(typeof(object));
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
generator.EmitCall(OpCodes.Callvirt, getMethod, null);
if (!propertyInfo.PropertyType.IsClass)
generator.Emit(OpCodes.Box, propertyInfo.PropertyType);
generator.Emit(OpCodes.Ret);
/*
* Create the delegate and return it
*/
return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
}
July 2, 2007 at 3:00 pm
[...] Check out Gerhard’s post here [...]
March 13, 2009 at 12:18 pm
Thanks for the idea! we are using this tecnique in our opensource ORM as well: http://www.signumframework.com/Reflection.ashx
The credit goes to you
March 27, 2009 at 1:59 pm
Its getting even better with Linq Expressions. The syntax is a better easier (but completely different) than IL code.
The method below will get either property or field:
static Func CreateGetPropValue(Type containerType, string propName)
{
var param = Expression.Parameter(typeof(object), “container”);
var func = Expression.Lambda(
Expression.Convert(
Expression.PropertyOrField(
Expression.Convert(
param,
containerType
),
propName
),
typeof(object)
),
param
);
return (Func)func.Compile();
}
July 5, 2009 at 12:37 am
[...] LCG for short, is accomplished via the DynamicMethod class. I found the perfect example to follow here. However I needed slightly different code to handle calling getters on a struct. It took me a while [...]
December 11, 2009 at 7:21 pm
This is superb, I have recently been delving into IL and I think this and other techniques are very under used by developers.
We recently exploited DynamicMethod to execute dynamically defined IL that enables us to get the offset of fields within managed structs, many people assume Marshal.OffsetOf does this but it doesn’t (it only gives offsets that fields will have after being marshaled).
I’m now looking to code equivalent logic to yours but for field access rather than property (not sure what kind of performance gains to expect here).
Anyway, thanks for a very useful article.
July 22, 2010 at 5:20 pm
Thanks for this article – I had been trying to do what you demonstrated for the past 3 hours and was ready to punch a hole through my wall.
November 25, 2010 at 8:02 am
[...] a combination of these, using reflection just once then a fast mapping code from then on based on Gerhard Stephan’s code emitting method. It is used with a DataTable like this: dt.Columns.AddRange(TableMapper<MyDTO>.Columns); : [...]
March 30, 2011 at 10:52 am
Great ! This is EXACTLY what I was searching for the last 48h… Actually Dror’s answer was also very useful since I wanted to run comparative performance test of my own using the three methods (GetValue, dynamic methods, LinQ Expressions). If any post on my blog come out of this, a link to your post is definitly gonna be in the the top of it !
October 26, 2011 at 2:00 pm
#If VBC_VER AndAlso VBC_VER >= 9.0 Then
‘ Use Dror’s method
#Else
‘ Use Gerhard’s IL method
#End If
Thanks you. This really worked in all instances I needed it in.
November 8, 2011 at 11:27 pm
[...] Above idea and most of the code are borrowed from this great post. [...]
August 2, 2012 at 9:36 am
Thanks for a greate idea, but, unfortunately, the code does not work at all for value type….
It eiither returns some garbage values, or fails with an exception.
For instance, you can test with a System.Drawing.Point type.
Any ideas for a workaround?
August 2, 2012 at 10:07 am
Dror ‘s method seems to work alright, the problem is that I have to target NET 2 at the moment, and migration to NET 4+ is nothing sure
August 6, 2012 at 8:26 am
I found the solution. The problem was that the default argument of reference type properties was not being unboxed correctly. Instead of being downcast, explicit unboxing should have been applied, as shown here bellow:
// Loading the first (and only) argument to the execution stack and downcasting it
// Note that a specific downcasting method is required for a reference-type object property
generator.Emit(OpCodes.Ldarg_0);
if ( propertyInfo.ReflectedType.IsClass || propertyInfo.ReflectedType.IsInterface )
generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
else
generator.Emit(OpCodes.Unbox, propertyInfo.ReflectedType);
This solution I found here:
http://stackoverflow.com/questions/1354368/dynamicmethod-returns-incorrect-value-when-property-type-is-int64
Important: Using ‘OpCodes.Unbox_Any’ would NOT do the trick (I checked it myself).
Note that checking ‘propertyInfo.ReflectedType.IsInterface’ might prove redundant.
Footnote: sorry if my post lacks any explanation — I am not yet knowledgeable in the ways of MSIL magik.
January 15, 2013 at 4:13 pm
I had a hard time getting the Set method to work in my VB program, I realized the issue was because my method needed to accept values of type Object, and would need to handle type conversion myself. I found that by default VB uses its own internal conversion functions to ensure the incoming argument is the correct type. Here is my updated If block for the Set method:
If PropInfo.PropertyType.IsClass Then
generator.Emit(OpCodes.Ldarg_1)
generator.Emit(OpCodes.Castclass, PropInfo.PropertyType)
ElseIf PropInfo.PropertyType.IsValueType Then
Dim Assembly = From x In System.AppDomain.CurrentDomain.GetAssemblies
Where x.FullName.Split(“,”)(0) = “Microsoft.VisualBasic”
Take 1
Dim Ass = Assembly.FirstOrDefault()
Dim Typ = Ass.GetType(“Microsoft.VisualBasic.CompilerServices.Conversions”)
Dim meths = Typ.GetMethods()
Dim Meth = From x In meths Where x.ReturnType Is PropInfo.PropertyType And
x.GetParameters.Count > 0 AndAlso
x.GetParameters(0).ParameterType Is GetType(Object)
If Meth.Count = 0 Then
generator.Emit(OpCodes.Ldarg_1)
generator.Emit(OpCodes.Unbox_Any, PropInfo.PropertyType)
Else
generator.Emit(OpCodes.Ldarg_1)
generator.Emit(OpCodes.Call, Meth.First)
End If
End If
generator.Emit(OpCodes.Callvirt, setMethod)
generator.Emit(OpCodes.Ret)