Introduction
Some time ago, I found a very interesting article "Using DynamicObject to implement general proxy classes". It was not the first proxy solution based on DynamicObject
and I started thinking how I could unify all solutions and improve performance. This article is a result of my investigations.
My solution solves some problems and adds some features to the original solution:
- Faster access to properties than Reflection
- Access to the fields the same way as to the properties and optional access to the non-public members
- Possibility of using custom implemented proxy instead of Reflection based proxy
I extracted the basic dynamic proxy logic and separated a single dynamic proxy into a chain of dynamic and static proxies. The dynamic proxy inherits DynamicObject
and wraps the IStaticProxy
interface to allow access using dynamic syntax. The static proxy defines access to the underlying object and to its properties and methods. You can implement a custom proxy and pass it to the dynamic proxy. Default implementation of static proxy is ReflectionProxy
.
Performance
Access via Reflection is very slow. To improve performance, I developed two ideas: property cache and expression based property access. Instead of Type.GetProperty()
or Type.GetProperties().First(...)
, I use the much faster Dictionary
as cache. Instead of using PropertyInfo.GetValue()
or SetValue()
, I compile getter or setter lambda expression and call it. It is much slower for single usage because of compilation time, but much faster for multiple usages.
Brief Description of DynamicObject
The DynamicObject
class enables you to define which operations can be performed on dynamic objects and how to perform those operations. For example, you can define what happens when you try to get
or set
an object property or call a method.
This class can be useful if you want to create a more convenient interface for a module. For example, instead of ugly syntax like obj.SetProperty("Count", 1)
, you can provide the ability to use much simpler syntax, like obj.Count = 1
. The property name will be resolved in runtime. To implement the dynamic behavior, you can inherit from the DynamicObject
class and override necessary methods. For example, if you need operations for setting and getting properties, you can override the TrySetMember
and TryGetMember
methods.
We will use this for wrapping a static proxy. Accessing any property will be via static proxy. Depending on the static proxy implementation, you can access the property of the object or e.g. field in database or entry in INI file, etc.
Class Diagram of the Library
Dimmed classes are code from the original article adapted to the new base functionality. I place this code here just because of compatibility issues, but the original code and changes can be found in the original article. I made some changes in names and structure of this code. DynamicProxy
was split into NotifyingProxy
and new DynamicProxy
, where the first one received slim functionality of INotifyPropertyChanged
interface and the second one received all basic functionality of wrapping static proxy and underlying object. Instead of using two ValidationProxy
classes, I extracted major validation functionality into IPropertyValidator
interface and created two corresponding implementations. This schema allows derived class DataErrorInfoProxy
to work with both of them or with any custom implementation.
DynamicProxy
This is the base class for further implementations of wrappers. It provides a simple implementation to access the properties of the underlying object via IStaticProxy
interface:
public class DynamicProxy : DynamicObject
{
public DynamicProxy(IStaticProxy proxy)
{
if (proxy == null)
throw new ArgumentNullException("proxy");
Proxy = proxy;
}
public DynamicProxy(object proxiedObject)
{
if (proxiedObject == null)
throw new ArgumentNullException("proxiedObject");
Proxy = proxiedObject is IStaticProxy ?
(IStaticProxy)proxiedObject : new ReflectionProxy(proxiedObject);
}
protected IStaticProxy Proxy { get; private set; }
protected virtual void SetMember(string propertyName, object value)
{ Proxy.SetProperty(propertyName, value); }
protected virtual object GetMember(string propertyName)
{ return Proxy.GetProperty(propertyName); }
public override bool TryConvert(ConvertBinder binder, out object result)
{
Debug.Assert(Proxy != null);
if (binder.Type == typeof(IStaticProxy))
{
result = Proxy;
return true;
}
if (Proxy.ProxiedObject != null &&
binder.Type.IsAssignableFrom(Proxy.ProxiedObject.GetType()))
{
result = Proxy.ProxiedObject;
return true;
}
return base.TryConvert(binder, out result);
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
Debug.Assert(Proxy != null);
try
{
result = GetMember(binder.Name);
return true;
}
catch(Exception ex)
{
throw new InvalidOperationException("Cannot get member", ex);
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Debug.Assert(Proxy != null);
try
{
SetMember(binder.Name, value);
return true;
}
catch(Exception ex)
{
throw new InvalidOperationException("Cannot set member", ex);
}
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
Debug.Assert(Proxy != null);
return Proxy.TryInvokeMethod(binder.Name, out result, args) ||
base.TryInvokeMember(binder, args, out result);
}
}
Points of Interest
The method TryConvert
is implemented to allow some special actions when the user implicitly tries to convert a DynamicProxy
object to another type. If the object is converted to the type of the underlying object, it returns the underlying object. When converting to the interface IStaticProxy
it returns the static proxy object.
IStaticProxy
This is the interface for further implementation of the access to the different types of entities.
public interface IStaticProxy
{
object ProxiedObject { get; }
Type GetPropertyType(string propertyName);
void SetProperty(string propertyName, object value);
object GetProperty(string propertyName);
bool TrySetProperty(string propertyName, object value);
bool TryGetProperty(string propertyName, out object result);
object InvokeMethod(string methodName, params object[] args);
bool TryInvokeMethod(string methodName, out object result, params object[] args);
}
ReflectionProxy
It implements IStaticProxy
and allows access to the members and methods of the underlying object using Reflection engine of the .NET.
public class ReflectionProxy : IStaticProxy
{
protected readonly TypeInfoCache TypeInfoCache;
protected readonly bool FullAccess;
public ReflectionProxy(object proxiedObject, bool fullAccess = false,
Type forceType = null)
{
if (proxiedObject == null) throw new ArgumentNullException("proxiedObject");
ProxiedObject = proxiedObject;
if (forceType != null)
{
if (!ProxiedObject.GetType().IsSubclassOf(forceType) &&
ProxiedObject.GetType() != forceType)
throw new ArgumentException("Forced type should be super
class of the object type");
}
else
forceType = ProxiedObject.GetType();
TypeInfoCache = GlobalTypeInfoCache.GetTypeInfo(forceType);
FullAccess = fullAccess;
}
[...]
#region IStaticProxy implementation
public object ProxiedObject { get; private set; }
public Type GetPropertyType(string propertyName)
{
var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
if (pie == null)
throw new ArgumentException("Property " + propertyName +
" doesn't exist in type " + TypeInfoCache.Type);
return pie.Type;
}
public virtual void SetProperty(string propertyName, object value)
{
var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
if (pie == null)
throw new ArgumentException("Property " + propertyName +
" doesn't exist in type " + TypeInfoCache.Type);
var setter = pie.FastSetter;
if (setter == null)
{
if (FullAccess) setter = pie.Setter;
if (setter == null)
throw new ArgumentException("Property " + propertyName +
" doesn't have write access in type " + TypeInfoCache.Type);
}
if (pie.Type != value.GetType())
value = Convert.ChangeType(value, pie.Type);
setter(ProxiedObject, value);
}
public virtual object GetProperty(string propertyName)
{
var pie = TypeInfoCache.GetPropertyInfoEx(propertyName, FullAccess);
if (pie == null)
throw new ArgumentException("Property " + propertyName +
" doesn't exist in type " + TypeInfoCache.Type);
var getter = pie.FastGetter;
if (getter == null)
{
if (FullAccess) getter = pie.Getter;
if (getter == null)
throw new ArgumentException("Property " + propertyName +
" doesn't have read access in type " + TypeInfoCache.Type);
}
return getter(ProxiedObject);
}
public virtual bool TrySetProperty(string propertyName, object value)
{
[...]
}
public virtual bool TryGetProperty(string propertyName, out object result)
{
[...]
}
public virtual object InvokeMethod(string methodName, params object[] args)
{
[...]
}
public virtual bool TryInvokeMethod(string methodName,
out object result, params object[] args)
{
[...]
}
#endregion
}
Points of Interest
It treats fields as properties. If a property with a specified name is not found, it searches for the field with the same name.
Parameter FullAccess
allows access to non-public members. It uses global type info cache to improve reflection performance.
TypeInfoCache
This class caches PropertyInfo
and FieldInfo
reflection information in the Dictionary
.
public class TypeInfoCache
{
[...]
private readonly Dictionary<string, PropertyInfoEx> _propertyInfoMap =
new Dictionary<string, PropertyInfoEx>();
public readonly Type Type;
public TypeInfoCache(Type type) { Type = type; }
private const BindingFlags DefaultLookup =
BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
public PropertyInfoEx GetPropertyInfoEx
(string propertyName, bool fullAccess = false)
{
PropertyInfoEx pie;
if (!_propertyInfoMap.TryGetValue(propertyName, out pie))
{
PropertyInfo pi = GetPropertyInfo
(propertyName, DefaultLookup | BindingFlags.NonPublic);
if (pi != null)
_propertyInfoMap.Add(propertyName, pie = new PropertyInfoEx(pi));
else
{
FieldInfo fi = GetFieldInfo
(propertyName, DefaultLookup | BindingFlags.NonPublic);
if (fi != null)
_propertyInfoMap.Add(propertyName, pie = new PropertyInfoEx(fi));
}
}
if (pie != null && (fullAccess || pie.IsPublic)) return pie;
return null;
}
private PropertyInfo GetPropertyInfo(string propertyName, BindingFlags lookup)
{
try
{
return Type.GetProperty(propertyName, lookup);
}
catch (Exception)
{
foreach (var propertyInfo in Type.GetProperties(lookup))
if (propertyInfo.Name == propertyName)
return propertyInfo;
}
return null;
}
private FieldInfo GetFieldInfo(string fieldName, BindingFlags lookup)
{
[...]
}
[...]
}
PropertyInfoEx
This inner class unifies access to the property and the field and also holds setters and getters for the member.
public class PropertyInfoEx
{
internal PropertyInfoEx(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null, "PropertyInfo should be specified");
PropertyInfo = propertyInfo;
IsPublic = propertyInfo.GetGetMethod() != null ||
propertyInfo.GetSetMethod() != null;
}
internal PropertyInfoEx(FieldInfo fieldInfo)
{
Debug.Assert(fieldInfo != null, "FieldInfo should be specified");
FieldInfo = fieldInfo;
IsPublic = !fieldInfo.IsPrivate && fieldInfo.IsPublic;
}
public PropertyInfo PropertyInfo { get; private set; }
public FieldInfo FieldInfo { get; private set; }
#region FastGetter
private bool _getterInaccessible;
private Func<object, object> _getter;
public Func<object, object> FastGetter
{
get
{
if (_getter == null && !_getterInaccessible)
{
_getter = PropertyInfo != null ?
PropertyInfo.GetValueGetter<object>() : FieldInfo.GetValueGetter<object>();
if (_getter == null) _getterInaccessible = true;
}
return _getter;
}
}
#endregion
#region FastSetter
private bool _setterInaccessible;
private Action<object, object> _setter;
public Action<object, object> FastSetter
{
get
{
if (_setter == null && !_setterInaccessible)
{
_setter = PropertyInfo != null ?
PropertyInfo.GetValueSetter<object>() : FieldInfo.GetValueSetter<object>();
if (_setter == null) _setterInaccessible = true;
}
return _setter;
}
}
#endregion
#region Getter
public Func<object, object> Getter
{
get
{
if (PropertyInfo == null || PropertyInfo.CanRead) return TheGetter;
return null;
}
}
private object TheGetter(object theObject)
{ return PropertyInfo != null
? PropertyInfo.GetValue(theObject, null)
: FieldInfo.GetValue(theObject); }
#endregion
#region Setter
public Action<object, object> Setter
{
get
{
if (PropertyInfo != null)
{
if (PropertyInfo.CanWrite)
return TheSetter;
}
else if (!FieldInfo.IsInitOnly)
return TheSetter;
return null;
}
}
public void TheSetter(object theObject, object value)
{
if (PropertyInfo != null)
PropertyInfo.SetValue(theObject, value, null);
else FieldInfo.SetValue(theObject, value);
}
#endregion
public bool IsPublic { get; private set; }
public Type Type { get { return PropertyInfo != null ?
PropertyInfo.PropertyType : FieldInfo.FieldType; } }
}
Points of Interest
FastSetter
and FastGetter
allows fast access to public members by using dynamically compiled lambda expressions instead of access via reflection.
FieldInfoExtensions and PropertyInfoExtensions
These classes implement two extension functions each.
Func<object, T> GetValueGetter<T>()
Action<object, T> GetValueSetter<T>()
These functions dynamically build lambda expressions to get
or set
specified fields or properties.
public static class PropertyInfoExtensions
{
public static Func<object, T> GetValueGetter<T>(this PropertyInfo propertyInfo)
{
if (!propertyInfo.CanRead || propertyInfo.GetGetMethod() == null) return null;
var instance = Expression.Parameter(typeof(Object), "i");
var castedInstance = Expression.ConvertChecked
(instance, propertyInfo.DeclaringType);
var property = Expression.Property(castedInstance, propertyInfo);
var convert = Expression.Convert(property, typeof(T));
var expression = Expression.Lambda(convert, instance);
return (Func<object, T>)expression.Compile();
}
public static Action<object, T> GetValueSetter<T>(this PropertyInfo propertyInfo)
{
if (!propertyInfo.CanWrite || propertyInfo.GetSetMethod() == null) return null;
var instance = Expression.Parameter(typeof(Object), "i");
var castedInstance = Expression.ConvertChecked
(instance, propertyInfo.DeclaringType);
var argument = Expression.Parameter(typeof(T), "a");
var setterCall = Expression.Call(
castedInstance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return Expression.Lambda<Action<object, T>>
(setterCall, instance, argument).Compile();
}
}
public static class FieldInfoExtensions
{
public static Func<object, T> GetValueGetter<T>(this FieldInfo fieldInfo)
{
if (!fieldInfo.IsPublic) return null;
var instance = Expression.Parameter(typeof(Object), "i");
var castedInstance = Expression.ConvertChecked
(instance, fieldInfo.DeclaringType);
var field = Expression.Field(castedInstance, fieldInfo);
var convert = Expression.Convert(field, typeof(T));
var expression = Expression.Lambda(convert, instance);
return (Func<object, T>)expression.Compile();
}
public static Action<object, T> GetValueSetter<T>(this FieldInfo fieldInfo)
{
if (!fieldInfo.IsPublic || fieldInfo.IsInitOnly) return null;
var instance = Expression.Parameter(typeof(Object), "i");
var castedInstance = Expression.ConvertChecked
(instance, fieldInfo.DeclaringType);
var argument = Expression.Parameter(typeof(T), "a");
var setter = Expression.Assign(
Expression.Field(castedInstance, fieldInfo),
Expression.Convert(argument, fieldInfo.FieldType));
return Expression.Lambda<Action<object, T>>
(setter, instance, argument).Compile();
}
}
FullAccessProxy
This class gives access to the non-public members of the underlying object.
internal class FullAccessProxy : DynamicProxy
{
public FullAccessProxy(object o, Type forceType = null) :
base(new ReflectionProxy(o, true, forceType)) { }
}
Points of Interest
This class originally was complicated enough and almost totally duplicated the functionality of the DynamicProxy
and ReflectionProxy
classes, except speed improving features. In addition, it was very similar to the DynamicProxy
from the original article. Now it contains only a constructor that restricts proxy to ReflectionProxy
with FullAccess
rights.
Performance Measuring
The original code uses construction object.GetType().GetProperties().First(...)
to get PropertyInfo
. Usage of the Dictionary
is 100 times faster and about 5-7 times faster than usage of object.GetType().GetProperty(...)
.
Usage of the compiled lambda expressions is about 10 times faster than Reflection. However, compilation of expression is about 300 times slower. Because of it, we compile expression once per property on the first access.
Anyway, we cannot reach closer to the performance of the direct property access. In my tests, static proxy access is about 100 times slower and dynamic proxy access is about 150-300 times slower than direct access.
On the other hand, if we measure property set with implemented INotifyPropertyChanged
, it looks much better. In this case, dynamic access is just 2-4 times slower.
The class DynamicObject
has some nice features. One of them is the possibility to declare real properties in derived classes and access them the same way as virtual properties. Reflection checks the real properties first and only if there is none matching property found, it will call TryGetMember
. However, it can cause unanticipated problem. Property in the Proxy can hide property in the underlying object and make it totally unavailable if the names are the same. Therefore, I try to initialize most parameters in the constructor.
Things to be Done
- Implement reflection info caching for methods
- Invoke methods via expressions
History