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

Fast Dynamic Property/Field Accessors

Rate me:
Please Sign up or sign in to vote.
4.94/5 (35 votes)
4 Jul 20063 min read 190K   2.1K   137   38
Fast run-time access to Property/Field values using the new (.NET 2.0) DynamicMethod class, and a very small amount of emitted IL code.

Introduction

Quite often, I run across the need to dynamically retrieve the value of an object's property (or field), where the actual member might not be known until run-time.

Some examples of where one might use this are:

  • Comparer for sorting an array/list/collection
  • Class to filter a collection based on member values
  • Class to serialize/deserialize a list of objects (faster than BinaryFormatter)

Background

In the past, I have made use of run-time reflection to: find a property's PropertyInfo or a field's FieldInfo, and Invoke the GetValue method of the PropertyInfo or FieldInfo on an object to retrieve the value.

C#
PropertyInfo pi = typeof(Widget).GetProperty("ID");
Int32 id = (Int32) pi.GetValue(widget, null);

This works fine if you only have to retrieve the member's value for a single object; but once you have to get the value in a loop, it performs very slow.

The New Method (.NET 2.0)

I got to thinking, why not generate (at run-time) the same IL code that the compiler generates at compile time, and use that to call the property's get accessor or retrieve the field's value?

In previous versions of .NET (1.0 and 1.1), there is a pretty big overhead to generating IL code. This is because using the System.Reflection.Emit namespace requires an out-of-process call to csc.exe to actually generate the IL Code.

Thankfully, .NET 2.0 provides us with a new DynamicMethod class, that is a much more light-weight method of generating IL code.

Using this new DynamicMethod class, it is easy to produce a fast method for dynamically retrieving member values.

Usage

C#
TypeUtility<Widget>.MemberGetDelegate<Int32>
GetID = TypeUtility<Widget>.GetMemberGetDelegate<Int32>("ID");
Int32 id = GetID(widget);

The Code

C#
public class TypeUtility<ObjectType>
{
    public delegate MemberType 
           MemberGetDelegate<MemberType>(ObjectType obj);

    public static MemberGetDelegate<MemberType>
        GetMemberGetDelegate<MemberType>(string memberName)
    {
        Type objectType = typeof(ObjectType);
    
        PropertyInfo pi = objectType.GetProperty(memberName);
        FieldInfo fi = objectType.GetField(memberName);
        if (pi != null)
        {
            // Member is a Property...

            MethodInfo mi = pi.GetGetMethod();
            if (mi != null)
            {
                // NOTE:  As reader J. Dunlap pointed out...
                //  Calling a property's get accessor is faster/cleaner using
                //  Delegate.CreateDelegate rather than Reflection.Emit 
                return (MemberGetDelegate<MemberType>)
                    Delegate.CreateDelegate(typeof(
                          MemberGetDelegate<MemberType>), mi);
            }
            else
                throw new Exception( String.Format(
                    "Property: '{0}' of Type: '{1}' does" + 
                    " not have a Public Get accessor",
                    memberName, objectType.Name ) );
        }
        else if (fi != null)
        {
            // Member is a Field...
    
            DynamicMethod dm = new DynamicMethod("Get" + memberName,
                typeof(MemberType), new Type[] { objectType }, objectType);
            ILGenerator il = dm.GetILGenerator();
            // Load the instance of the object (argument 0) onto the stack
            il.Emit(OpCodes.Ldarg_0);
            // Load the value of the object's field (fi) onto the stack
            il.Emit(OpCodes.Ldfld, fi);
            // return the value on the top of the stack
            il.Emit(OpCodes.Ret);

            return (MemberGetDelegate<MemberType>)
                dm.CreateDelegate(typeof(MemberGetDelegate<MemberType>));
        }
        else
            throw new Exception( String.Format(
                "Member: '{0}' is not a Public Property or Field of Type: '{1}'",
                memberName, objectType.Name ));
    }
}

Tweaking Performance - Caching the Generated Code

There is a small amount of build up time that occurs during each call to GetMemberGetDelegate. This is due to having to generate the IL code and create a delegate. If possible, the generated delegate should be kept around for future use. Below is the code that can be used for that purpose.

C#
public class TypeUtility<ObjectType>
{
    private static Dictionary<string,Delegate>
    _memberGetDelegates = new Dictionary<string,Delegate>();

    public static MemberGetDelegate<MemberType> 
        GetCachedMemberGetDelegate<MemberType>(string memberName)
    {
        if ( _memberGetDelegates.ContainsKey(memberName) )
            return (MemberGetDelegate<MemberType>) 
                   _memberGetDelegates[memberName];

        MemberGetDelegate<MemberType> returnValue = 
             GetMemberGetDelegate<MemberType>( memberName );
        lock ( _memberGetDelegates )
        {
            _memberGetDelegates[memberName] = returnValue;
        }
        return returnValue;
    }
}

Performance Test Results

Performance Test Screen Shot

Based on my performance tests, using the dynamically generated code is 30x-50x faster than using normal reflection. It is still only about half as fast as using compiled code, which I imagine is due to the number of method calls. With compiled code, there is only one method call: the property's get accessor. When using the run-time generated code, you end up making two method calls: first to the generated code, and second to the property's get accessor. Depending on your exact usage, you may be able to optimize further and get back to one method call.

An Example Using This Concept

I mentioned earlier that one example of where this concept could come in handy is in the case of a generic comparer used to sort a collection or a list.

Consider the following:

C#
List<Widget> widgets = GetWidgetsListFromSomewhere();
// Sort widgets by ID
widgets.Sort( new MemberComparer<Widget>("ID") );
// Now re-sort widgets by Name
widgets.Sort( new MemberComparer<Widget>("Name") );

You can see how easy this makes it to sort your collection based on a dynamic property or field. For example, you could easily wire this up to a UI to allow for fast sorting based on the column a user clicked on.

Here is the code for a simple MemberComparer class:

Note: This could easily be extended to support chaining of fields and sort directions together.

C#
public class MemberComparer<ObjectType> : IComparer<ObjectType>
{
    private delegate int CompareDelegate(ObjectType x, ObjectType y);
    private CompareDelegate _compare;

    public MemberComparer(string memberName)
    {
        _compare = GetCompareDelegate(memberName);
    }

    public int Compare(ObjectType x, ObjectType y)
    {
        return _compare(x, y);
    }

    private CompareDelegate GetCompareDelegate(string memberName)
    {
        Type objectType = typeof(ObjectType);

        PropertyInfo pi = objectType.GetProperty(memberName);
        FieldInfo fi = objectType.GetField(memberName);
        Type memberType = null;
        bool isProperty = false;

        if (pi != null)
        {
            if (pi.GetGetMethod() != null)
            {
                memberType = pi.PropertyType;
                isProperty = true;
            }
            else
                throw new Exception(String.Format(
                    "Property: '{0}' of Type: '{1}' " + 
                    "does not have a Public Get accessor",
                    memberName, objectType.Name));
        }
        else if (fi != null)
            memberType = fi.FieldType;
        else
            throw new Exception(String.Format(
                "'{0}' is not a Public Field or Property" + 
                " with a Get accessor for Type: '{1}' ",
                memberName, objectType.Name));

        Type comparerType = 
          typeof(Comparer<>).MakeGenericType(
          new Type[] { memberType });
        MethodInfo getDefaultMethod = 
          comparerType.GetProperty("Default").GetGetMethod();
        MethodInfo compareMethod = 
          getDefaultMethod.ReturnType.GetMethod("Compare");

        DynamicMethod dm = 
           new DynamicMethod("Compare_" + memberName, typeof(int),
           new Type[] { objectType, objectType }, comparerType);
        ILGenerator il = dm.GetILGenerator();

        // Load Comparer<memberType>.Default onto the stack
        il.EmitCall(OpCodes.Call, getDefaultMethod, null);

        // Load the member from arg 0 onto the stack
        il.Emit(OpCodes.Ldarg_0);
        if (isProperty)
            il.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);
        else
            il.Emit(OpCodes.Ldfld);

        // Load the member from arg 1 onto the stack
        il.Emit(OpCodes.Ldarg_1);
        if (isProperty)
            il.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);
        else
            il.Emit(OpCodes.Ldfld);

        // Call the Compare method
        il.EmitCall(OpCodes.Callvirt, compareMethod, null);
        
        il.Emit(OpCodes.Ret);

        return (CompareDelegate)dm.CreateDelegate(
                           typeof(CompareDelegate));
    }
}

Assuming you want to sort the Widget class, the CompareDelegate that gets dynamically created contains the dynamic equivalent of the following code:

C#
// To Sort by Name property
public int Compare(Widget x, Widget y)
{
 return System.Collections.Generic.Comparer<string>.Default.Compare(
                                                            x.Name, y.Name);
}
// To Sort by ID property
public int Compare(Widget x, Widget y)
{
 return System.Collections.Generic.Comparer<int>.Default.Compare(x.ID, y.ID);
}

Conclusion

There are many other places that this concept can be used in place of the old slow method of reflection. With this article, I have only scratched the surface of what can be done. Hopefully, I have at least given some insight into how powerful Reflection.Emit can be. I leave it up to the reader, however, to find other places that they might be able to use similar code and/or concepts.

History

  • 07/04/2006 - Updated code to also work with fields, and added a generic comparer example.
  • 06/23/2006 - Created initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I am currently working as an Enterprise Architect at TicketsNow.com.

I have been designing and developing web/windows based software applications and utilities for 8+ years.

And here is a random picture of my dog. Wink | ;)

Comments and Discussions

 
GeneralMy vote of 5 Pin
akramKamal21-Oct-12 12:10
akramKamal21-Oct-12 12:10 
Questionneed help for SetFieldDelegate Pin
Bib3477027-Nov-11 6:58
Bib3477027-Nov-11 6:58 
AnswerRe: need help for SetFieldDelegate Pin
Member 261964718-Aug-17 2:10
Member 261964718-Aug-17 2:10 
GeneralMemberComparer and LINQ Pin
Anthony Bouch29-Jun-08 2:58
Anthony Bouch29-Jun-08 2:58 
GeneralSmall addition to DynamicPropertyManager Pin
Anastasiosyal22-Jun-07 2:25
Anastasiosyal22-Jun-07 2:25 
QuestionDynamic types? Pin
MaidenGuy13-Jun-07 23:50
MaidenGuy13-Jun-07 23:50 
AnswerRe: Dynamic types? Pin
Stephen Erisman15-Jun-07 11:17
Stephen Erisman15-Jun-07 11:17 
QuestionHow about Set Pin
scotteg24-Apr-07 3:57
scotteg24-Apr-07 3:57 
GeneralCultureInfo aware MemberComparer class help Pin
Nikolaj Lynge Olsson28-Feb-07 10:34
Nikolaj Lynge Olsson28-Feb-07 10:34 
NewsProblems with structs (and solved!) Pin
unbird16-Feb-07 2:58
unbird16-Feb-07 2:58 
GeneralRe: Problems with structs (and solved!) Pin
Stephen Erisman16-Feb-07 11:06
Stephen Erisman16-Feb-07 11:06 
GeneralRe: Problems with structs (and solved!) Pin
davojc14-Feb-08 2:02
davojc14-Feb-08 2:02 
GeneralSetting the values of properties Pin
sma29-Nov-06 22:24
sma29-Nov-06 22:24 
GeneralRe: Setting the values of properties Pin
mgaerber17-Dec-06 23:10
mgaerber17-Dec-06 23:10 
QuestionRe: Setting the values of properties [modified] Pin
Digital Ric13-Jun-07 16:30
Digital Ric13-Jun-07 16:30 
GeneralRe: Setting the values of properties Pin
Pete Samwel3-Dec-07 17:36
Pete Samwel3-Dec-07 17:36 
Questiondotnet 1.1 Pin
aibo8-Aug-06 0:30
aibo8-Aug-06 0:30 
AnswerRe: dotnet 1.1 Pin
Stephen Erisman8-Aug-06 4:19
Stephen Erisman8-Aug-06 4:19 
QuestionRe: dotnet 1.1 Pin
aibo8-Aug-06 4:46
aibo8-Aug-06 4:46 
QuestionNested objects Pin
Mikeoff11-Jul-06 2:38
Mikeoff11-Jul-06 2:38 
Does this solution work with nested sub-objects? For example I want to compare Invoices by client name as in:

invoice.Client.Name

Where invoice has a property named Client which is of type Client. Client in turn has a property named Name.
AnswerRe: Nested objects Pin
Stephen Erisman11-Jul-06 4:38
Stephen Erisman11-Jul-06 4:38 
QuestionFieldInfo not passed to Ldfld? Pin
Nathan Baulch6-Jul-06 18:33
Nathan Baulch6-Jul-06 18:33 
AnswerRe: FieldInfo not passed to Ldfld? Pin
Stephen Erisman7-Jul-06 5:43
Stephen Erisman7-Jul-06 5:43 
QuestionPurpose Pin
TheSire3-Jul-06 23:33
TheSire3-Jul-06 23:33 
AnswerRe: Purpose Pin
Stephen Erisman4-Jul-06 12:44
Stephen Erisman4-Jul-06 12:44 

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.