Click here to Skip to main content
11,479,486 members (48,845 online)
Click here to Skip to main content

Fast Dynamic Property Access with C#

, 22 Mar 2005 CPOL 223.3K 1.8K 151
Rate this:
Please Sign up or sign in to vote.
Reflecting on Properties is nice, but often it can be too slow. This article describes an alternative method for dynamic property access.

Sample Image - Fast_Dynamic_Properties.png

Introduction

Reflection is very useful for dynamic processing. However, if you have to repeatedly reflect on a property, say in a processing loop, you'll soon find that it can lead to performance problems. I ran into this particular problem in developing a rules engine that will be able to validate collections. I thought I would share this snippet of code, since I think it could be used in a variety of situations.

In this article I'll provide a fast, alternative solution for dynamic property access.

Implementation

My goal was to develop a class that could create a Type at runtime for direct access to the Get and Set methods of a property. This class would be provided with the target object Type and the property name it should access. I had considered runtime compiling, but then I learned about Reflection.Emit and its ability to create types at runtime through the use of MSIL. This was my first experience writing MSIL code and I found Ben Ratzlaff's Populating a PropertyGrid using Reflection.Emit to be a very helpful start.

In order to be able to compile code against against a Type that will be generated at runtime, an interface had to be created to define the generated types.

/// <summary>
/// The IPropertyAccessor interface defines a property
/// accessor.
/// </summary>
public interface IPropertyAccessor
{
    /// <summary>
    /// Gets the value stored in the property for
    /// the specified target.
    /// </summary>
    /// <param name="target">Object to retrieve
    /// the property from.</param>
    /// <returns>Property value.</returns>
    object Get(object target);
    /// <summary>
    /// Sets the value for the property of
    /// the specified target.
    /// </summary>
    /// <param name="target">Object to set the
    /// property on.</param>
    /// <param name="value">Property value.</param>
    void Set(object target, object value);
}

The concrete PropertyAccessor class generates a Type at runtime that conforms to this interface and serves as a proxy layer to the generated Type. In its constructor it simply needs to be provided with the target object Type and the name of the property it should provide access to. All of the Reflection.Emit code is performed in the EmitAssembly method.

/// <summary>
/// The PropertyAccessor class provides fast dynamic access
/// to a property of a specified target class.
/// </summary>
public class PropertyAccessor : IPropertyAccessor
{
    /// <summary>
    /// Creates a new property accessor.
    /// </summary>
    /// <param name="targetType">Target object type.</param>
    /// <param name="property">Property name.</param>
    public PropertyAccessor(Type targetType, string property)
    {
        this.mTargetType = targetType;
        this.mProperty = property;
        PropertyInfo propertyInfo = 
            targetType.GetProperty(property);
        //
        // Make sure the property exists
        //
        if(propertyInfo == null)
        {
            throw new 
              PropertyAccessorException(string.Format("Property \"{0}\" does" + 
              " not exist for type " + "{1}.", property, targetType));
        }
        else
        {
            this.mCanRead = propertyInfo.CanRead;
            this.mCanWrite = propertyInfo.CanWrite;
            this.mPropertyType = propertyInfo.PropertyType;
        }
    }

    /// <summary>
    /// Gets the property value from the specified target.
    /// </summary>
    /// <param name="target">Target object.</param>
    /// <returns>Property value.</returns>
    public object Get(object target)
    {
        if(mCanRead)
        {
            if(this.mEmittedPropertyAccessor == null)
            {
                this.Init();
            }
            return this.mEmittedPropertyAccessor.Get(target);
        }
        else
        {
            throw new 
              PropertyAccessorException(string.Format("Property \"{0}\" does" + 
              " not have a get method.", mProperty));
        }
    }

    /// <summary>
    /// Sets the property for the specified target.
    /// </summary>
    /// <param name="target">Target object.</param>
    /// <param name="value">Value to set.</param>
    public void Set(object target, object value)
    {
        if(mCanWrite)
        {
            if(this.mEmittedPropertyAccessor == null)
            {
                this.Init();
            }
            //
            // Set the property value
            //
            this.mEmittedPropertyAccessor.Set(target, value);
        }
        else
        {
            throw new 
              PropertyAccessorException(string.Format("Property \"{0}\" does" + 
              " not have a set method.", mProperty));
        }
    }

    /// <summary>
    /// Whether or not the Property supports read access.
    /// </summary>
    public bool CanRead
    {
        get
        {
            return this.mCanRead;
        }
    }

    /// <summary>
    /// Whether or not the Property supports write access.
    /// </summary>
    public bool CanWrite
    {
        get
        {
            return this.mCanWrite;
        }
    }

    /// <summary>
    /// The Type of object this property accessor was
    /// created for.
    /// </summary>
    public Type TargetType
    {
        get
        {
            return this.mTargetType;
        }
    }

    /// <summary>
    /// The Type of the Property being accessed.
    /// </summary>
    public Type PropertyType
    {
        get
        {
            return this.mPropertyType;
        }
    }

    private Type mTargetType;
    private string mProperty;
    private Type mPropertyType;
    private IPropertyAccessor mEmittedPropertyAccessor;
    private Hashtable mTypeHash;
    private bool mCanRead;
    private bool mCanWrite;

    /// <summary>
    /// This method generates creates a new assembly containing
    /// the Type that will provide dynamic access.
    /// </summary>
    private void Init()
    {
        this.InitTypes();
        // Create the assembly and an instance of the 
        // property accessor class.
        Assembly assembly = EmitAssembly();
        mEmittedPropertyAccessor = 
          assembly.CreateInstance("Property") as IPropertyAccessor;
        if(mEmittedPropertyAccessor == null)
        {
            throw new Exception("Unable to create property accessor.");
        }
    }

    /// <summary>
    /// Thanks to Ben Ratzlaff for this snippet of code
    /// http://www.codeproject.com/cs/miscctrl/CustomPropGrid.asp
    /// 
    /// "Initialize a private hashtable with type-opCode pairs 
    /// so i dont have to write a long if/else statement when outputting msil"
    /// </summary>
    private void InitTypes()
    {
        mTypeHash=new Hashtable();
        mTypeHash[typeof(sbyte)]=OpCodes.Ldind_I1;
        mTypeHash[typeof(byte)]=OpCodes.Ldind_U1;
        mTypeHash[typeof(char)]=OpCodes.Ldind_U2;
        mTypeHash[typeof(short)]=OpCodes.Ldind_I2;
        mTypeHash[typeof(ushort)]=OpCodes.Ldind_U2;
        mTypeHash[typeof(int)]=OpCodes.Ldind_I4;
        mTypeHash[typeof(uint)]=OpCodes.Ldind_U4;
        mTypeHash[typeof(long)]=OpCodes.Ldind_I8;
        mTypeHash[typeof(ulong)]=OpCodes.Ldind_I8;
        mTypeHash[typeof(bool)]=OpCodes.Ldind_I1;
        mTypeHash[typeof(double)]=OpCodes.Ldind_R8;
        mTypeHash[typeof(float)]=OpCodes.Ldind_R4;
    }

    /// <summary>
    /// Create an assembly that will provide the get and set methods.
    /// </summary>
    private Assembly EmitAssembly()
    {
        //
        // Create an assembly name
        //
        AssemblyName assemblyName = new AssemblyName();
        assemblyName.Name = "PropertyAccessorAssembly";
        //
        // Create a new assembly with one module
        //
        AssemblyBuilder newAssembly = 
           Thread.GetDomain().DefineDynamicAssembly(assemblyName, 
           AssemblyBuilderAccess.Run);
        ModuleBuilder newModule = 
           newAssembly.DefineDynamicModule("Module");
        //
        // Define a public class named "Property" in the assembly.
        //
        TypeBuilder myType = 
           newModule.DefineType("Property", TypeAttributes.Public);
        //
        // Mark the class as implementing IPropertyAccessor. 
        //
        myType.AddInterfaceImplementation(typeof(IPropertyAccessor));
        // Add a constructor
        ConstructorBuilder constructor = 
           myType.DefineDefaultConstructor(MethodAttributes.Public);
        //
        // Define a method for the get operation. 
        //
        Type[] getParamTypes = new Type[] {typeof(object)};
        Type getReturnType = typeof(object);
        MethodBuilder getMethod = 
          myType.DefineMethod("Get", 
          MethodAttributes.Public | MethodAttributes.Virtual, 
          getReturnType, 
          getParamTypes);
        //
        // From the method, get an ILGenerator. This is used to
        // emit the IL that we want.
        //
        ILGenerator getIL = getMethod.GetILGenerator();

        //
        // Emit the IL. 
        //
        MethodInfo targetGetMethod = this.mTargetType.GetMethod("get_" + 
                                                    this.mProperty);
        if(targetGetMethod != null)
        {
            getIL.DeclareLocal(typeof(object));
            getIL.Emit(OpCodes.Ldarg_1); //Load the first argument
            //(target object)
            //Cast to the source type
            getIL.Emit(OpCodes.Castclass, this.mTargetType);
            //Get the property value
            getIL.EmitCall(OpCodes.Call, targetGetMethod, null);
            if(targetGetMethod.ReturnType.IsValueType)
            {
                getIL.Emit(OpCodes.Box, targetGetMethod.ReturnType);
                //Box if necessary
            }
            getIL.Emit(OpCodes.Stloc_0); //Store it

            getIL.Emit(OpCodes.Ldloc_0);
        }
        else
        {
            getIL.ThrowException(typeof(MissingMethodException));
        }
        getIL.Emit(OpCodes.Ret);

        //
        // Define a method for the set operation.
        //
        Type[] setParamTypes = new Type[] {typeof(object), typeof(object)};
        Type setReturnType = null;
        MethodBuilder setMethod = 
            myType.DefineMethod("Set", 
           MethodAttributes.Public | MethodAttributes.Virtual, 
           setReturnType, 
           setParamTypes);
        //
        // From the method, get an ILGenerator. This is used to
        // emit the IL that we want.
        //
        ILGenerator setIL = setMethod.GetILGenerator();
        //
        // Emit the IL. 
        //
        MethodInfo targetSetMethod = 
            this.mTargetType.GetMethod("set_" + this.mProperty);
        if(targetSetMethod != null)
        {
            Type paramType = targetSetMethod.GetParameters()[0].ParameterType;
            setIL.DeclareLocal(paramType);
            setIL.Emit(OpCodes.Ldarg_1); //Load the first argument 
            //(target object)
            //Cast to the source type
            setIL.Emit(OpCodes.Castclass, this.mTargetType);            
            setIL.Emit(OpCodes.Ldarg_2); //Load the second argument 
            //(value object)
            if(paramType.IsValueType)
            {
                setIL.Emit(OpCodes.Unbox, paramType); //Unbox it 
                if(mTypeHash[paramType]!=null) //and load
                {
                    OpCode load = (OpCode)mTypeHash[paramType];
                    setIL.Emit(load);
                }
                else
                {
                    setIL.Emit(OpCodes.Ldobj,paramType);
                }
            }
            else
            {
                setIL.Emit(OpCodes.Castclass, paramType); //Cast class
            }

            setIL.EmitCall(OpCodes.Callvirt, 
               targetSetMethod, null); //Set the property value
        }
        else
        {
            setIL.ThrowException(typeof(MissingMethodException));
        }
        setIL.Emit(OpCodes.Ret);
        //
        // Load the type
        //
        myType.CreateType();
        return newAssembly;
    }
}

Discussion

Although the PropertyAccessor class must reflect on the target Type the first time the property is accessed (for either read or write), this reflection only has to be done once. All subsequent calls to Get or Set will use the generated IL code.

Run the sample project for a performance demonstration. I've also included an NUnit test fixture.

License

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

Share

About the Author

James Nies
Software Developer Astral Softworks, LLC
United States United States
James Nies is a graduate of the University of Wyoming's Department of Computer Science. He is currently employed as a software developer and has been programming in C#, his favorite language, for the last 5 years. James recently formed a small software development and web design company, Astral Softworks, LLC.



Comments and Discussions

 
QuestionDoes not work for virtual property overrides Pin
Member 367761918-Apr-13 8:24
memberMember 367761918-Apr-13 8:24 
QuestionMagnifying Property Access Pin
ExcellentOrg7-Apr-13 11:37
groupExcellentOrg7-Apr-13 11:37 
SuggestionUsing of Generics Pin
lordkbc27-Jun-11 2:16
memberlordkbc27-Jun-11 2:16 
GeneralJust thought I'll mention this Pin
Martin Lottering17-May-11 3:06
memberMartin Lottering17-May-11 3:06 
QuestionWhat if I want to create the properties dynamically Pin
harsh_godha18-Oct-10 8:07
memberharsh_godha18-Oct-10 8:07 
QuestionDynamic properties work fine but what about dynamic Methods and ctors? [modified] Pin
Doomii5-Aug-08 11:50
memberDoomii5-Aug-08 11:50 
GeneralVariant with lambda Pin
Maskaev9-Apr-08 4:05
memberMaskaev9-Apr-08 4:05 
GeneralRe: Variant with lambda Pin
Anastasiosyal22-Oct-08 3:54
memberAnastasiosyal22-Oct-08 3:54 
GeneralRe: Variant with lambda Pin
Maskaev22-Oct-08 4:37
memberMaskaev22-Oct-08 4:37 
GeneralRe: Variant with lambda Pin
Member 45815422-Jan-10 11:59
memberMember 45815422-Jan-10 11:59 
Generalpagefile grows boundlessly Pin
bobman7719-Sep-07 17:35
memberbobman7719-Sep-07 17:35 
GeneralRe: pagefile grows boundlessly Pin
James Nies22-Sep-07 22:33
memberJames Nies22-Sep-07 22:33 
GeneralModuleBuilder's DefineType throws exception occasionally Pin
Mandeep Singh Bhatia5-Jun-07 0:12
memberMandeep Singh Bhatia5-Jun-07 0:12 
Hi,
I have been using this code snippet & it worked perfect in .net 1.0 & 1.1. But ever since I ported to 2.0 there is a strange problem:

AssemblyBuilder newAssembly = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder newModule = newAssembly.DefineDynamicModule("Module");
TypeBuilder myType = newModule.DefineType("Property", TypeAttributes.Public);


It throws exception in DefineType.
Say this code is executed 200 time. It will run fine 199 times but once it will throw exception. But it will surely throw exception everytime I run.
And it throws exception only when I debug using VS2005 not when I directly run.

Does anyone have any clue to this problem.

thanks,
Mandeep.

GeneralQuick Port to Generics Pin
Tobias Hertkorn30-May-06 16:20
memberTobias Hertkorn30-May-06 16:20 
QuestionRe: Quick Port to Generics Pin
Leela Krishna21-Jan-07 0:34
memberLeela Krishna21-Jan-07 0:34 
GeneralRe: Quick Port to Generics Pin
Tobias Hertkorn13-Apr-07 11:52
memberTobias Hertkorn13-Apr-07 11:52 
GeneralProblem with Value Types Pin
Wasp.NET11-May-06 23:26
memberWasp.NET11-May-06 23:26 
QuestionModify to getting all properties Pin
savasorama6-Jan-06 4:21
membersavasorama6-Jan-06 4:21 
AnswerRe: Modify to getting all properties Pin
sgheeren16-May-06 7:27
membersgheeren16-May-06 7:27 
GeneralCall method Pin
NetTry9-Nov-05 4:18
memberNetTry9-Nov-05 4:18 
QuestionSetting null values gives an Exception Pin
Patrick Heymans2-Nov-05 22:35
memberPatrick Heymans2-Nov-05 22:35 
AnswerRe: Setting null values gives an Exception Pin
James Nies6-Nov-05 12:23
memberJames Nies6-Nov-05 12:23 
GeneralExtended for field accessors Pin
sgheeren22-Jul-05 5:54
membersgheeren22-Jul-05 5:54 
GeneralRe: Extended for field accessors Pin
Marc Brooks15-May-06 9:24
memberMarc Brooks15-May-06 9:24 
GeneralRe: Extended for field accessors Pin
sgheeren16-May-06 6:37
membersgheeren16-May-06 6:37 
AnswerRe: Extended for field accessors [modified] Pin
sgheeren26-May-06 5:31
membersgheeren26-May-06 5:31 
GeneralRe: Extended for field accessors Pin
sgheeren16-May-06 6:50
membersgheeren16-May-06 6:50 
Generalcongratulations!! and a question Pin
rurum22-Jun-05 13:10
memberrurum22-Jun-05 13:10 
GeneralRe: congratulations!! and a question Pin
James Nies15-Jul-05 20:34
memberJames Nies15-Jul-05 20:34 
GeneralNice Pin
reinux4-Apr-05 11:42
memberreinux4-Apr-05 11:42 
GeneralA few optimizations Pin
Kris Vandermotten29-Mar-05 9:12
memberKris Vandermotten29-Mar-05 9:12 
GeneralRe: A few optimizations Pin
James Nies29-Mar-05 13:47
memberJames Nies29-Mar-05 13:47 
GeneralRe: A few optimizations Pin
Steve Hansen24-May-06 0:03
memberSteve Hansen24-May-06 0:03 
Questionwhat license? Pin
Roger J29-Mar-05 1:04
memberRoger J29-Mar-05 1:04 
AnswerRe: what license? Pin
James Nies31-Mar-05 19:41
memberJames Nies31-Mar-05 19:41 
Generalvs Reflection Pin
smoot425-Mar-05 17:15
membersmoot425-Mar-05 17:15 
GeneralRe: vs Reflection Pin
James Nies28-Mar-05 13:00
memberJames Nies28-Mar-05 13:00 
Generalrules engine Pin
Marc Sommer24-Mar-05 20:34
memberMarc Sommer24-Mar-05 20:34 
GeneralRe: rules engine Pin
James Nies25-Mar-05 10:06
memberJames Nies25-Mar-05 10:06 
GeneralSmall bug Pin
Dale Thompson24-Mar-05 11:07
memberDale Thompson24-Mar-05 11:07 
GeneralRe: Small bug Pin
James Nies25-Mar-05 9:52
memberJames Nies25-Mar-05 9:52 
GeneralRe: Small bug Pin
Dale Thompson28-Mar-05 4:00
memberDale Thompson28-Mar-05 4:00 
GeneralVery cool! Pin
Marc Brooks23-Mar-05 6:23
memberMarc Brooks23-Mar-05 6:23 
GeneralTime tests seem to be flawed... Pin
cjbreisch23-Mar-05 3:04
membercjbreisch23-Mar-05 3:04 
GeneralRe: Time tests seem to be flawed... Pin
leppie23-Mar-05 4:20
memberleppie23-Mar-05 4:20 
GeneralRe: Time tests seem to be flawed... Pin
James Nies23-Mar-05 5:09
memberJames Nies23-Mar-05 5:09 
GeneralRe: Time tests seem to be flawed... Pin
sgheeren16-May-06 6:55
membersgheeren16-May-06 6:55 
GeneralCool :) Pin
leppie23-Mar-05 2:45
memberleppie23-Mar-05 2:45 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150520.1 | Last Updated 23 Mar 2005
Article Copyright 2005 by James Nies
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid