Abstract
I need to access property value on a object with Reflection API. It is very useful to create for example a generic Deep Clone API on POCO object.
BUT :
- Sometimes, Setter method of the object is not public.
- I need the very very high performance to do it.
- It seems the better way to do is Deletage.CreateDelegate method but it is important to use Invoke() method on it. DynamicMethod is very poor performance approach.
- I don’t want use IL approach because it is too hard to develop and maintains.
I propose helper based PropertyInfo, which support the interface IPropertyAccessor. This interface has a method GetValue and SetValue.
A solution
I create a small helper to encapsulate a delegate in generic decorator that’s support my own IPropertyAccessor interface :
IPropertyAccessor Interface is simple :
public interface IPropertyAccessor
{
PropertyInfo PropertyInfo { get; }
string Name { get; }
object GetValue(object source)
void SetValue(object source, object value);
}
An example :
// UneClasse : a simple class with an Int property PropertyA
var c = new UneClasse();
c.PropertyA= 12345;
PropertyInfo piA = typeof(UneClasse).GetProperty("PropertyA");
var paA = PropertyInfoHelper.GetFastAccessor(piA);
Assert.AreEqual(c.FieldA, 12345); // Ok !
Assert.AreEqual(piA.GetValue(c, null), 12345); // Ok !
Assert.AreEqual(paA.GetValue(c), 12345); // Ok !
paA.SetValue(c, 54321);
Assert.AreEqual(c.PropertyA, 54321); // Ok !
Assert.AreEqual(piA.GetValue(c, null), 54321); // Ok !
Assert.AreEqual(paA.GetValue(c), 54321); // Ok !
Performance
Some tests show importants differences between differences approachs.
NB : all duration is done with 100 000 call on my computer.
Direct call : | ~4 ms. |
PropertyInfo.SetValue on public setter | ~300 ms. |
PropertyInfo.SetValue on private setter | ~1400 ms. |
Stronged delegate on SetValue MethodInfo (Private or public) | ~5 ms. |
Delegate call with DynamicInvoke (Private or public) | ~1500 ms. |
IPropertyAccessor helper | ~9 ms. |
- the better way, of course, is direct call : (4 ms)
myInstance.PropertyA = 1234;
- Use SetValue method on PropertyInfo (282 ms) (Setter is public)
PropertyInfo piA = typeof(UneClasse).GetProperty("PropertyA");
…
piA.SetValue(myInstance, 1234, null);
- Use SetValue method on PropertyInfo (1391 ms) (Setter is private)
- Use strong typed Delegate created with Delegate.CreateDelegate() : 4 ms !! (same of direct call and even if Setter is private)
var setMethodA = piA.GetSetMethod(true);
var _setHandlerATyped = (SetValueHandler<UneClasse,int>)Delegate.CreateDelegate(typeof(SetValueHandler<UneClasse,int>),
setMethodA);
…
_setHandlerATyped.Invoke(myInstance, 1234)
- Use my helper : 9 ms !!
IPropertyAccessor paA = PropertyInfoHelper.GetFastAccessor(piA);
…
paA.SetValue(myInstance, 1234)
Compare “CreateDelegate” versus “dynamic IL” during intialisation process
in System.Web.dll, you can find an internal class “System.Web.Util.FastPropertyAccessor”.This class use Emit functions to generate dynamic assembly and dynamic code.
I compare this approach (with little reflection to acces internal members) with CreateDelegate approach in console application and 5000 Test class with One property.
The result is :
IL Approach (System.Web.Util .FastPropertyAccessor) | CreateDelegate Approach (IPropertyAccessor) | |
Duration | 11920 ms. | 1337 ms. |
Memory delta | ~32300 Ko | ~6000 Ko |
The code of the helper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace Tools.Reflection
{
///
/// Représentation d'une propriété pour accélerer de maniére considérable les écritures/lectures par reflection
/// sur les propriétés d'un object
///
public interface IPropertyAccessor
{
///
/// La propriété concernée
///
PropertyInfo PropertyInfo { get; }
///
/// Le nom de cette propriété
///
string Name { get; }
///
/// Récupération de la valeur de la propriété
///
/// l'object concerné
///valeur de la propriété
object GetValue(object source);
///
/// Mise en place d'une nouvelle valeur dans la propriété
///
/// l'object concerné
/// la nouvelle valeur pour la propriété
void SetValue(object source, object value);
}
///
/// Classe helper pour obtenir l'accesseur sur une propriété donnée
///
public static class PropertyInfoHelper
{
private static Dictionary_cache = new Dictionary ();
///
/// Obtention du helper pour acceder au getter/setter de la propriété
///
///
///
public static IPropertyAccessor GetFastAccessor(PropertyInfo propertyInfo)
{
IPropertyAccessor result;
lock (_cache)
{
if (!_cache.TryGetValue(propertyInfo, out result))
{
result = CreateAccessor(propertyInfo);
_cache.Add(propertyInfo, result); ;
}
}
return result;
}
///
/// public pour les tests de performances
///
///
///
public static IPropertyAccessor CreateAccessor(PropertyInfo propertyInfo)
{
return (IPropertyAccessor)Activator.CreateInstance(
typeof(PropertyWrapper<,>).MakeGenericType
(propertyInfo.DeclaringType, propertyInfo.PropertyType), propertyInfo);
}
}
///
/// Classe concrete implémentant IPropertyAccessor
///
///
///
internal class PropertyWrapper: IPropertyAccessor
{
private PropertyInfo _propertyInfo;
private Func_getMethod;
private Action_setMethod;
///
/// Constructeur public
///
/// la propriété à encapsulé
public PropertyWrapper(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
MethodInfo mGet = propertyInfo.GetGetMethod(true);
MethodInfo mSet = propertyInfo.GetSetMethod(true);
// Rq : on peut par se biais acceder aussi aux accesseur privé
// tous les aspects liés à la sécurité est donc pris en charge par CreateDelegate
// et non à chaque appel à GetMethod/SetMethod
_getMethod = (Func)Delegate.CreateDelegate
(typeof(Func), mGet);
_setMethod = (Action)Delegate.CreateDelegate
(typeof(Action), mSet);
}
object IPropertyAccessor.GetValue(object source)
{
return _getMethod((TObject)source);
}
void IPropertyAccessor.SetValue(object source, object value)
{
_setMethod((TObject)source, (TValue)value);
}
///
/// Voir
///
public string Name
{
get
{
return _propertyInfo.Name;
}
}
///
/// Voir
///
public PropertyInfo PropertyInfo
{
get
{
return _propertyInfo;
}
}
}
}
TODO List
- test on real application : It is just a prototype !
- improve with this[] property
References
- Discussion : http://stackoverflow.com/questions/724143/how-do-i-create-a-delegate-for-a-net-property
- Discussion : http://stackoverflow.com/questions/1308642/delegate-createdelegate-without-prototype
- Reference : http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx