I was writing a framework that required access to an application-specific set of fields/properties. The straight-forward approach is to use the GetValue
/SetValue
methods on FieldInfo
and PropertyInfo
. But everyone knows Reflection isn't the most performance savvy way to go about things. So I decided to write something that was faster.
First, I wanted to use Expressions. But I quickly realized that there were some real enhancements done for Expressions in 4.0, and I was limited to 3.5 (I'm doing work that needs to be included in Unity3d / Mono projects). The major issue was the lack of the Expression.Assign
method found so nicely in 4.0, but sadly missing in 3.5.
Using the help of a surrogate assignment function, I worked around this limitation to come up with the following class. By constructing an object of this Type with a MemberInfo
object (either FieldInfo
or PropertyInfo
), the object takes the place of the SetValue
and GetValue
from Reflection. The overall improvement of this class to Reflection was thirty-seven (37) times faster execution times. That's significant.
Of note- I could have had the application send my framework two delegates / lambdas that performed the get
/set
operations. This would have been better for performance, but worse for maintainability (two things instead of one that can get crossed-up) and worse for flexibility (what if the field names were in data somewhere and unknown at compile time).
The application must pass in a MemberInfo
object from Reflection when it constructs an object of this Type
.
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace Oberberger.Morpheus
{
public class CGetterSetter
{
private static MethodInfo sm_valueAssignerMethod =
typeof( CGetterSetter )
.GetMethod( "ValueAssigner", BindingFlags.Static | BindingFlags.NonPublic );
private static void ValueAssigner<T>( out T dest, T src )
{
dest = src;
}
private Func<object, object> m_getter;
private Action<object, object> m_setter;
public object Get( object p_obj )
{
return m_getter( p_obj );
}
public void Set( object p_obj, object p_value )
{
m_setter( p_obj, p_value );
}
public CGetterSetter( MemberInfo p_member )
{
if (p_member == null)
throw new ArgumentNullException( "Must initialize with a non-null Field or Property" );
MemberExpression exMember = null;
if (p_member is FieldInfo)
{
var fi = p_member as FieldInfo;
var assignmentMethod = sm_valueAssignerMethod.MakeGenericMethod( fi.FieldType );
Init( fi.DeclaringType, fi.FieldType,
_ex => exMember = Expression.Field( _ex, fi ),
( _, _val ) => Expression.Call( assignmentMethod,
exMember, _val )
);
}
else if (p_member is PropertyInfo)
{
var pi = p_member as PropertyInfo;
var assignmentMethod = pi.GetSetMethod();
Init( pi.DeclaringType, pi.PropertyType,
_ex => exMember = Expression.Property( _ex, pi ),
( _obj, _val ) => Expression.Call
( _obj, assignmentMethod, _val )
);
}
else
{
throw new ArgumentException
( "The member must be either a Field or a Property, not " + p_member.MemberType );
}
}
private void Init(
Type p_objectType,
Type p_valueType,
Func<Expression, MemberExpression> p_fnGetMember,
Func<Expression, Expression, MethodCallExpression> p_fnMakeCallExpression )
{
var exObjParam = Expression.Parameter( typeof( object ), "theObject" );
var exValParam = Expression.Parameter( typeof( object ), "theProperty" );
var exObjConverted = Expression.Convert( exObjParam, p_objectType );
var exValConverted = Expression.Convert( exValParam, p_valueType );
Expression exMember = p_fnGetMember( exObjConverted );
Expression getterMember = p_valueType.IsValueType ?
Expression.Convert( exMember, typeof( object ) ) : exMember;
m_getter = Expression.Lambda<Func<object, object>>( getterMember, exObjParam ).Compile();
Expression exAssignment = p_fnMakeCallExpression( exObjConverted, exValConverted );
m_setter = Expression.Lambda<Action<object, object>>
( exAssignment, exObjParam, exValParam ).Compile();
}
#if false // The following code was refactored because of the extreme similarities between the methods.
public CGenGetterSetter( MemberInfo p_member )
{
if (p_member == null)
throw new ArgumentNullException( "Must initialize with a non-null Field or Property" );
if (p_member is FieldInfo)
InitAsField( p_member as FieldInfo );
else if (p_member is PropertyInfo)
InitAsProperty( p_member as PropertyInfo );
else
throw new ArgumentException
( "The member must be either a Field or a Property, not " + p_member.MemberType );
}
private void InitAsProperty( PropertyInfo p_propertyInfo )
{
var objType = p_propertyInfo.DeclaringType;
var valType = p_propertyInfo.PropertyType;
var assignmentMethod = p_propertyInfo.GetSetMethod();
var exObjParam = Expression.Parameter( typeof( object ), "theObject" );
var exValParam = Expression.Parameter( typeof( object ), "theProperty" );
var exObjConverted = Expression.Convert( exObjParam, objType );
var exValConverted = Expression.Convert( exValParam, valType );
Expression exMember = Expression.Property( exObjConverted, p_propertyInfo );
Expression getterMember = valType.IsValueType ?
Expression.Convert( exMember, typeof( object ) ) : exMember;
m_getter = Expression.Lambda<Func<object, object>>( getterMember, exObjParam ).Compile();
Expression exAssignment = Expression.Call
( exObjConverted, assignmentMethod, exValConverted );
m_setter = Expression.Lambda<Action<object, object>>
( exAssignment, exObjParam, exValParam ).Compile();
}
private void InitAsField( FieldInfo p_fieldInfo )
{
var objType = p_fieldInfo.DeclaringType;
var valType = p_fieldInfo.FieldType;
var assignmentMethod = typeof( CGenGetterSetter )
.GetMethod( "ValueAssigner", BindingFlags.Static | BindingFlags.NonPublic )
.MakeGenericMethod( valType );
var exObjParam = Expression.Parameter( typeof( object ), "theObject" );
var exValParam = Expression.Parameter( typeof( object ), "theProperty" );
var exObjConverted = Expression.Convert( exObjParam, objType );
var exValConverted = Expression.Convert( exValParam, valType );
Expression exMember = Expression.Field( exObjConverted, p_fieldInfo );
Expression getterMember = valType.IsValueType ?
Expression.Convert( exMember, typeof( object ) ) : exMember;
m_getter = Expression.Lambda<Func<object, object>>( getterMember, exObjParam ).Compile();
var exAssignment = Expression.Call( assignmentMethod, exMember, exValConverted );
m_setter = Expression.Lambda<Action<object, object>>
( exAssignment, exObjParam, exValParam ).Compile();
}
#endif
}
}