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



dimanche 21 février 2010

Footer on WPF DataGrid: by use several synchronized datagrid

Introduction

Last year, I proposed a version of WPF DataGrid with Footer.

But this version can not apply on new WPF 4.0 Datagrid (because i was make too much changes in source code of the component)

With this new approach, I use several DataGrid in the same Window, and I synchronizes each one on :

  • Scrolling event (horizontal and vertical)
  • Drag and drop column header
  • Change width of column on main DataGrid

Even if it is more complex to apply for each Data window (It is necessary to instanciate several DataGrid, set relatation between each one, etc.) , It seems pretty good because :

  • It can apply on new version WPF 4.0 Datagrid
  • It can manage too the merge header of multiple column (cf demo)
  • It can use to do something like “Microsoft Excel" to freeze some column or row in a global Data grid !

Screenshot of the demo :

ScreenShot2

Download

Download v1.00 here

  • WPF 3.5 version (on Visual Studio 2008)
  • WPF 4.0 version (on Visual Studio 2010 Beta)

API :

To do this, It is necessary to :

  • Have the same number of column in each DataGrid (except if you merge header column with specific attached property)

<tk:DataGrid.Columns>
    <tk:DataGridTextColumn local:AssociatedDataGrid.ColumnSpan="2"
                           Binding="{Binding A}"/>

  • I use attached Dependency Property to set the link between main grid and another grid

<tk:DataGrid
             Name="xMain"
             Grid.Row="2"
             Grid.Column="2"
             ItemsSource="{Binding DataSource}"
             local:AssociatedDataGrid.Bottom="{Binding ElementName=xBottom}"
             local:AssociatedDataGrid.Top="{Binding ElementName=xTop}"
             local:AssociatedDataGrid.Right="{Binding ElementName=xRight}"
             local:AssociatedDataGrid.Left="{Binding ElementName=xLeft}"
             AutoGenerateColumns="False">

  • and it’s all !

My demo is a demo … I think lot of thing is not finished by main idea is in it !

Best regards

Thibaud