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.
public interface IPropertyAccessor
{
object Get(object target);
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.
public class PropertyAccessor : IPropertyAccessor
{
public PropertyAccessor(Type targetType, string property)
{
this.mTargetType = targetType;
this.mProperty = property;
PropertyInfo propertyInfo =
targetType.GetProperty(property);
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;
}
}
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));
}
}
public void Set(object target, object value)
{
if(mCanWrite)
{
if(this.mEmittedPropertyAccessor == null)
{
this.Init();
}
this.mEmittedPropertyAccessor.Set(target, value);
}
else
{
throw new
PropertyAccessorException(string.Format("Property \"{0}\" does" +
" not have a set method.", mProperty));
}
}
public bool CanRead
{
get
{
return this.mCanRead;
}
}
public bool CanWrite
{
get
{
return this.mCanWrite;
}
}
public Type TargetType
{
get
{
return this.mTargetType;
}
}
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;
private void Init()
{
this.InitTypes();
Assembly assembly = EmitAssembly();
mEmittedPropertyAccessor =
assembly.CreateInstance("Property") as IPropertyAccessor;
if(mEmittedPropertyAccessor == null)
{
throw new Exception("Unable to create property accessor.");
}
}
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;
}
private Assembly EmitAssembly()
{
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "PropertyAccessorAssembly";
AssemblyBuilder newAssembly =
Thread.GetDomain().DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.Run);
ModuleBuilder newModule =
newAssembly.DefineDynamicModule("Module");
TypeBuilder myType =
newModule.DefineType("Property", TypeAttributes.Public);
myType.AddInterfaceImplementation(typeof(IPropertyAccessor));
ConstructorBuilder constructor =
myType.DefineDefaultConstructor(MethodAttributes.Public);
Type[] getParamTypes = new Type[] {typeof(object)};
Type getReturnType = typeof(object);
MethodBuilder getMethod =
myType.DefineMethod("Get",
MethodAttributes.Public | MethodAttributes.Virtual,
getReturnType,
getParamTypes);
ILGenerator getIL = getMethod.GetILGenerator();
MethodInfo targetGetMethod = this.mTargetType.GetMethod("get_" +
this.mProperty);
if(targetGetMethod != null)
{
getIL.DeclareLocal(typeof(object));
getIL.Emit(OpCodes.Ldarg_1);
getIL.Emit(OpCodes.Castclass, this.mTargetType);
getIL.EmitCall(OpCodes.Call, targetGetMethod, null);
if(targetGetMethod.ReturnType.IsValueType)
{
getIL.Emit(OpCodes.Box, targetGetMethod.ReturnType);
}
getIL.Emit(OpCodes.Stloc_0);
getIL.Emit(OpCodes.Ldloc_0);
}
else
{
getIL.ThrowException(typeof(MissingMethodException));
}
getIL.Emit(OpCodes.Ret);
Type[] setParamTypes = new Type[] {typeof(object), typeof(object)};
Type setReturnType = null;
MethodBuilder setMethod =
myType.DefineMethod("Set",
MethodAttributes.Public | MethodAttributes.Virtual,
setReturnType,
setParamTypes);
ILGenerator setIL = setMethod.GetILGenerator();
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);
setIL.Emit(OpCodes.Castclass, this.mTargetType);
setIL.Emit(OpCodes.Ldarg_2);
if(paramType.IsValueType)
{
setIL.Emit(OpCodes.Unbox, paramType);
if(mTypeHash[paramType]!=null)
{
OpCode load = (OpCode)mTypeHash[paramType];
setIL.Emit(load);
}
else
{
setIL.Emit(OpCodes.Ldobj,paramType);
}
}
else
{
setIL.Emit(OpCodes.Castclass, paramType);
}
setIL.EmitCall(OpCodes.Callvirt,
targetSetMethod, null);
}
else
{
setIL.ThrowException(typeof(MissingMethodException));
}
setIL.Emit(OpCodes.Ret);
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.