Introduction
This is an example of how to use the new DynamicMethod
class in .NET 2.0 as an alternative to Reflection. Specifically, this example shows how to instantiate an object
and get
/set
properties and fields on an object
. As an added bonus, this code sample also allows getting and setting private
fields and properties as well as instantiating object
s with private
constructors.
Background
At my current company we have built our own Object-Relational-Mapper (ORM), similar to nHibernate. In order to do this we were using reflection to set the properties/fields of an object
with the values we retrieved from the database using a DataReader. Also, one of the features that was important to us was the ability to have “Read Only” properties on our object
s. This involved setting the private
field with our data mapper, instead of the public
property. Unfortunately, reflection is a bit slow, especially when using it to set private
values.
Solution
Fortunately, along came .NET 2.0 and the DynamicMethod
class. This is a new class that allows one to create and compile code at run time. This approach is much faster then using reflection (see the times on the screen shot above). The down side to this approach is that you need to write your dynamic code using IL (as opposed to C#). But, with only a few lines of code we were able to create the desired effect.
Details
Here’s the important code in the included download (the other classes are just for demo).
public delegate object GetHandler(object source);
public delegate void SetHandler(object source, object value);
public delegate object InstantiateObjectHandler();
public sealed class DynamicMethodCompiler
{
private DynamicMethodCompiler() { }
internal static InstantiateObjectHandler CreateInstantiateObjectHandler(Type type)
{
ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
if (constructorInfo == null)
{
throw new ApplicationException(string.Format("The type {0} must declare an
empty constructor (the constructor may be private, internal,
protected, protected internal, or public).", type));
}
DynamicMethod dynamicMethod = new DynamicMethod("InstantiateObject",
MethodAttributes.Static |
MethodAttributes.Public, CallingConventions.Standard, typeof(object),
null, type, true);
ILGenerator generator = dynamicMethod.GetILGenerator();
generator.Emit(OpCodes.Newobj, constructorInfo);
generator.Emit(OpCodes.Ret);
return (InstantiateObjectHandler)dynamicMethod.CreateDelegate
(typeof(InstantiateObjectHandler));
}
internal static GetHandler CreateGetHandler(Type type, PropertyInfo propertyInfo)
{
MethodInfo getMethodInfo = propertyInfo.GetGetMethod(true);
DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
ILGenerator getGenerator = dynamicGet.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Call, getMethodInfo);
BoxIfNeeded(getMethodInfo.ReturnType, getGenerator);
getGenerator.Emit(OpCodes.Ret);
return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
}
internal static GetHandler CreateGetHandler(Type type, FieldInfo fieldInfo)
{
DynamicMethod dynamicGet = CreateGetDynamicMethod(type);
ILGenerator getGenerator = dynamicGet.GetILGenerator();
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Ldfld, fieldInfo);
BoxIfNeeded(fieldInfo.FieldType, getGenerator);
getGenerator.Emit(OpCodes.Ret);
return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler));
}
internal static SetHandler CreateSetHandler(Type type, PropertyInfo propertyInfo)
{
MethodInfo setMethodInfo = propertyInfo.GetSetMethod(true);
DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
ILGenerator setGenerator = dynamicSet.GetILGenerator();
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
UnboxIfNeeded(setMethodInfo.GetParameters()[0].ParameterType, setGenerator);
setGenerator.Emit(OpCodes.Call, setMethodInfo);
setGenerator.Emit(OpCodes.Ret);
return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
}
internal static SetHandler CreateSetHandler(Type type, FieldInfo fieldInfo)
{
DynamicMethod dynamicSet = CreateSetDynamicMethod(type);
ILGenerator setGenerator = dynamicSet.GetILGenerator();
setGenerator.Emit(OpCodes.Ldarg_0);
setGenerator.Emit(OpCodes.Ldarg_1);
UnboxIfNeeded(fieldInfo.FieldType, setGenerator);
setGenerator.Emit(OpCodes.Stfld, fieldInfo);
setGenerator.Emit(OpCodes.Ret);
return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler));
}
private static DynamicMethod CreateGetDynamicMethod(Type type)
{
return new DynamicMethod("DynamicGet", typeof(object),
new Type[] { typeof(object) }, type, true);
}
private static DynamicMethod CreateSetDynamicMethod(Type type)
{
return new DynamicMethod("DynamicSet", typeof(void),
new Type[] { typeof(object), typeof(object) }, type, true);
}
private static void BoxIfNeeded(Type type, ILGenerator generator)
{
if (type.IsValueType)
{
generator.Emit(OpCodes.Box, type);
}
}
private static void UnboxIfNeeded(Type type, ILGenerator generator)
{
if (type.IsValueType)
{
generator.Emit(OpCodes.Unbox_Any, type);
}
}
}
Each of these methods takes a Type
and returns an appropriate delegate
. This delegate
then points to a chunk of dynamically generated IL, which can be invoked to perform the needed task. For example, GetHandler(myInstance)
returns the value of the field/property that the handler was created for.
Unfortunately, I have a very limited understanding of IL, so I'm unable to give an in-depth explanation of what’s going on under the hood here. I had to do a fair amount of Googling, decompiling, and good old fashioned trial and error to write this code.
Here's what the delegate
you get back looks like:
public delegate object GetHandler(object source);
This delegate
takes one object
as an argument, which is the object
containing the property/field that you want to get the value of. The return is also an object
and will be the value of the property/field on the object
that was passed in. Here's an example of how to get the delegate
.
Type type = typeof(<your class>);
FieldInfo fieldInfo = type.GetField(<field or property name>, BindingFlags.Instance |
BindingFlags.NonPublic | BindingFlags.Public);
GetHandler getHandler = DynamicMethodCompiler.CreateGetHandler(type, fieldInfo);
And here's an example of how to use the delegate
once you have it.
MyClass myClass = new MyClass();
myClass.MyProperty = 4;
int currentValue = (int)getHandler(myClass);
In this example, currentValue
will equal 4
at the end of the code.
Note that there is still some reflection involved. You still need to get a reference to the appropriate reflection object
(e.g. FieldInfo
, PropertyInfo
). However, this is a one time cost. You only need to use reflection to create the Delegate
and after that everything is compiled. This is a much lower cost than using reflection every time you want to get
or set
a value.