dimanche 17 octobre 2010

Fast Property Accessor without dynamic IL

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