Writing a super fast deep-property reader/writer using delegates
February 16, 2009 .Net, Visual Studio 3 CommentsFor a while now I’ve been writing to write a non-reflection based property reader that allows me to read properties of an object in a deep hierarchy.
So lets say you have the following simple class hierarchy:
public class FirstLevel { public string Leaf { get; set; } } public class SecondLevel { public FirstLevel First { get; set; } } public class Root { public SecondLevel Prop { get; set; } }
Then you want to read the Leaf from the FirstLevel by looking at the Root level and all you have is the path to the property: “SecondLevel.FirstLevel.Leaf”.
Using reflection is a simple process of recursively going through the object, finding the proper property doing a GetValue on it and then repeating the process until you find your property. This is ok but it’s very slow. Very very slow.
Your other two alternatives if you don’t want the bear the hit of reflection is to code emit a method that would resemble something like this:
public static string GetLeaf(Root root) { SecondLevel secondLevel = root.Prop; if ( secondLevel != null ) { FirstLevel firstLevel = secondLevel.First; if ( firstLevel != null ) { return firstLevel.Leaf; } } return null; }
Or use dynamically created delegates to map to the get_Property and set_Property methods generated for each property.
using System; using System.Reflection; namespace ACorns.Utils { public interface IPropertyAccessor { object GetValue(object target); void SetValue(object target, object value); } public interface IPropertyAccessor<TargetObject, FinalProperty> { FinalProperty GetValue(TargetObject target); void SetValue(TargetObject target, FinalProperty value); } /// <summary> /// Super-dooper, super-fact deep property extractor. /// You can use it to get/set properties deep in an object hierarchy without using reflection. /// Please cache the returned IPropertyAccessor if you want to reuse it. /// Good performance is only achived with cached IPropertyAccessor(s)! /// /// Usage: IPropertyAccessor accessor = PropertyExtractor.GetAccessor(typeof(Root), "Prop.First.Leaf", true); /// accessor.GetValue(target); /// </summary> public static class PropertyExtractor { public static IPropertyAccessor<TargetObject, FinalProperty> GetAccessor<TargetObject, FinalProperty>(Type targetType, string propertyNames) { return GetAccessor<TargetObject, FinalProperty>(targetType, propertyNames, true); } public static IPropertyAccessor<TargetObject, FinalProperty> GetAccessor<TargetObject, FinalProperty>(Type targetType, string propertyNames, bool throwOnNull) { IPropertyAccessor internalPropertyAccessor = GetAccessor(targetType, propertyNames, throwOnNull); Type accessorType = typeof(TypedPropertyAccessor<,>).MakeGenericType(typeof(TargetObject), typeof(FinalProperty)); IPropertyAccessor<TargetObject, FinalProperty> propertyAccessor = (IPropertyAccessor<TargetObject, FinalProperty>)Activator.CreateInstance(accessorType, internalPropertyAccessor); return propertyAccessor; } public static IPropertyAccessor GetAccessor(Type targetType, string propertyNames, bool throwOnNull) { string[] deepPropertyNames = propertyNames.Split('.'); IPropertyAccessor internalPropertyAccessor = GetAccessor(targetType, deepPropertyNames, 0, throwOnNull); return internalPropertyAccessor; } private static IPropertyAccessor GetAccessor(Type targetType, string[] deepPropertyNames, int level, bool throwOnNull) { string property = deepPropertyNames[level]; PropertyInfo propertyInfo = targetType.GetProperty(property); // Create a delegate to a get_ method. The delegate looks like // Func<TargetType, PropertyType> func to a property like class TargetType { public PropertyType { get; } } Type getterDelegateType = typeof(Func<,>).MakeGenericType(targetType, propertyInfo.PropertyType); Delegate getDelegate = Delegate.CreateDelegate(getterDelegateType, propertyInfo.GetGetMethod()); IPropertyAccessor accessor; level++; if (level < deepPropertyNames.Length) { // Recursive detect the down the property IPropertyAccessor nextLevelAccessor = GetAccessor(propertyInfo.PropertyType, deepPropertyNames, level, throwOnNull); Type accessorType = typeof(PropertyAccessor<,>).MakeGenericType(targetType, propertyInfo.PropertyType); accessor = (IPropertyAccessor)Activator.CreateInstance(accessorType, getDelegate, nextLevelAccessor, throwOnNull); } else { Type setterDelegateType = typeof(Action<,>).MakeGenericType(targetType, propertyInfo.PropertyType); Delegate setDelegate = Delegate.CreateDelegate(setterDelegateType, propertyInfo.GetSetMethod()); Type accessorType = typeof(LeafPropertyAccessor<,>).MakeGenericType(targetType, propertyInfo.PropertyType); accessor = (IPropertyAccessor)Activator.CreateInstance(accessorType, getDelegate, setDelegate); } return accessor; } #region TypedPropertyAccessor internal sealed class TypedPropertyAccessor<T, U> : IPropertyAccessor<T, U> { private readonly IPropertyAccessor _next; public TypedPropertyAccessor(IPropertyAccessor next) { _next = next; } public U GetValue(T target) { return (U) _next.GetValue(target); } public void SetValue(T target, U value) { _next.SetValue(target, value); } } #endregion #region Recursive Property Accessors internal sealed class PropertyAccessor<T, U> : IPropertyAccessor { private readonly Func<T,U> _readDelegate; private readonly IPropertyAccessor _next; private readonly bool _throwOnNull; public PropertyAccessor(Func<T, U> readDelegate, IPropertyAccessor next, bool throwOnNull) { _readDelegate = readDelegate; _throwOnNull = throwOnNull; _next = next; } public object GetValue(object target) { object result = _readDelegate((T)target); if (result == null) { if (_throwOnNull) throw new NullReferenceException("Property '" + _readDelegate.Method.Name + "' on '" + typeof(T).Name + "' returned null."); else return default(U); } return _next.GetValue(result); } public void SetValue(object target, object value) { object result = _readDelegate((T)target); if (result == null) { if (_throwOnNull) throw new NullReferenceException("Property '" + _readDelegate.Method.Name + "' on '" + typeof(T).Name + "' returned null."); else return; } _next.SetValue((U)result, value); } } internal sealed class LeafPropertyAccessor<T, U> : IPropertyAccessor { private readonly Func<T, U> _readDelegate; private readonly Action<T, U> _setDelegate; public LeafPropertyAccessor(Func<T, U> readDelegate, Action<T, U> setDelegate) { _readDelegate = readDelegate; _setDelegate = setDelegate; } public object GetValue(object target) { object result = _readDelegate((T)target); return result; } public void SetValue(object target, object value) { _setDelegate((T)target, (U)value); } } #endregion } }
To use it you would request an IPropertyExtractor and then ask it to do a GetValue for you:
[TestMethod]
public void ExtractPropertyFromLeafFixture()
{
Root r = new Root();
r.Prop = new SecondLevel();
r.Prop.First = new FirstLevel();
r.Prop.First.Leaf = "original value";
IPropertyAccessor accessor = PropertyExtractor.GetAccessor(typeof(Root), "Prop.First.Leaf", false);
Assert.IsNotNull(accessor);
object value = accessor.GetValue(r);
Assert.AreEqual("original value", value.ToString());
accessor.SetValue(r, "new value");
value = accessor.GetValue(r);
Assert.AreEqual("new value", value.ToString());
}
The beauty of this approach is that once the IPropertyAccessor is created (using Reflection) in the beginning, the Get/Set on it is done via a set of direct delegate calls to the property and calls via the interface to the next level.
The callstack is thus very small and efficient:
Make sure you cache this accessor if you need it again at a later time as creating it is expensive.
The performance should be as good as you can get without using code emitting.
