Click here to Skip to main content
15,883,883 members
Articles / Programming Languages / C#
Article

Dynamic Code Generation vs Reflection

Rate me:
Please Sign up or sign in to vote.
4.69/5 (39 votes)
22 Jan 2007CPOL3 min read 223.5K   1.6K   140   42
A dynamic code generator for setting property and field values that outperforms reflection
Sample Image - DynamicCompilation.jpg

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 objects 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 objects. 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).

C#
public delegate object GetHandler(object source);
public delegate void SetHandler(object source, object value);
public delegate object InstantiateObjectHandler();

public sealed class DynamicMethodCompiler
{
    // DynamicMethodCompiler
    private DynamicMethodCompiler() { }

    // CreateInstantiateObjectDelegate
    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));
    }
    
    // CreateGetDelegate
    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));
    }

    // CreateGetDelegate
    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));
    }

    // CreateSetDelegate
    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));
    }

    // CreateSetDelegate
    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));
    }

    // CreateGetDynamicMethod
    private static DynamicMethod CreateGetDynamicMethod(Type type)
    {
        return new DynamicMethod("DynamicGet", typeof(object), 
              new Type[] { typeof(object) }, type, true);
    }

    // CreateSetDynamicMethod
    private static DynamicMethod CreateSetDynamicMethod(Type type)
    {
        return new DynamicMethod("DynamicSet", typeof(void), 
              new Type[] { typeof(object), typeof(object) }, type, true);
    }

    // BoxIfNeeded
    private static void BoxIfNeeded(Type type, ILGenerator generator)
    {
        if (type.IsValueType)
        {
            generator.Emit(OpCodes.Box, type);
        }
    }

    // UnboxIfNeeded
    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:

C#
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.

C#
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.

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Scratch Audio
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Help with DynamicMethod Pin
Dewey14-Apr-08 11:04
Dewey14-Apr-08 11:04 
GeneralRe: Help with DynamicMethod Pin
Herbrandson14-Apr-08 11:24
Herbrandson14-Apr-08 11:24 
GeneralRe: Help with DynamicMethod Pin
Dewey14-Apr-08 11:33
Dewey14-Apr-08 11:33 
GeneralRe: Help with DynamicMethod Pin
Dewey14-Apr-08 11:36
Dewey14-Apr-08 11:36 
GeneralRe: Help with DynamicMethod Pin
Herbrandson22-Apr-08 19:27
Herbrandson22-Apr-08 19:27 
GeneralRe: Help with DynamicMethod Pin
Dewey14-Apr-08 11:17
Dewey14-Apr-08 11:17 
General.NET 2.0 suggestions Pin
Steve Hansen22-Jan-07 21:37
Steve Hansen22-Jan-07 21:37 
GeneralRe: .NET 2.0 suggestions Pin
Herbrandson23-Jan-07 5:03
Herbrandson23-Jan-07 5:03 
Thanks for the feedback. I've just recently learned about the new Static class modifier. I've been converting classes in my source code as I find them. I just haven't updated this article with those changes yet.

The problem with Generics is that they don't really work in the scenarios this was built for. For example, if you know the type of class you want to instantiate at design time, you would most likely instantiate it directly (i.e. SimpleClass simpleClass = new SimpleClass()). I think the same thing would apply for Gets and Sets as well. If that’s not true, I could add an overload that would support Generics. If this would be helpful to anyone, let me know and I’ll update the code.

As far as “internal” goes, that just happens to be the way it’s used in the application I pulled this code from. Sorry, but at least it’s an easy change 

GeneralRe: .NET 2.0 suggestions Pin
James Curran23-Jan-07 16:25
James Curran23-Jan-07 16:25 
GeneralRe: .NET 2.0 suggestions Pin
Herbrandson30-Jan-07 15:58
Herbrandson30-Jan-07 15:58 
GeneralRe: .NET 2.0 suggestions Pin
Keith L Robertson26-Aug-08 8:04
Keith L Robertson26-Aug-08 8:04 
GeneralRe: .NET 2.0 suggestions Pin
James Curran26-Aug-08 13:55
James Curran26-Aug-08 13:55 
GeneralRe: .NET 2.0 suggestions Pin
Keith L Robertson27-Aug-08 11:42
Keith L Robertson27-Aug-08 11:42 
GeneralWrapper Class Pin
jdmartinez5-Sep-06 5:44
jdmartinez5-Sep-06 5:44 
Generalthe most useful post I've seen Pin
dave.dolan14-Aug-06 19:45
dave.dolan14-Aug-06 19:45 
GeneralGoods sample Pin
ISharp1-Aug-06 16:11
ISharp1-Aug-06 16:11 
AnswerRe: Goods sample Pin
Herbrandson1-Aug-06 19:06
Herbrandson1-Aug-06 19:06 
Generalmore examples Pin
Alexandre Brisebois28-Jul-06 12:05
Alexandre Brisebois28-Jul-06 12:05 
GeneralRe: more examples Pin
Herbrandson2-Aug-06 6:22
Herbrandson2-Aug-06 6:22 
GeneralVery Nice! Pin
JohnDeHope328-Jul-06 8:14
JohnDeHope328-Jul-06 8:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.