Je ne vais pas m’attarder sur ce qu’est la réflexion (ou reflection en anglais), de nombreux articles existent déjà sur le web. La partie de la reflection à laquelle nous allons nous intéresser dans cet article est l’accès aux membres (méthodes, propriétés ou champs) non publiques d’un type. En gros nous allons voir comment simplifier le code suivant :
PropertyInfo property = typeof (Sample).GetProperty("Name", BindingFlags.Instance | BindingFlags.NonPublic);
int value = (int)property.GetValue(foo);
Pour arriver à nos fins nous allons utiliser le type dynamic. Ce type est apparut avec .NET 4. Il permet d’indiquer au compilateur de ne pas vérifier les opérations que l’on effectue sur l’objet, les vérifications seront faites à l’exécution :
class Sample
{
void A()
{
dynamic instance = new Sample();
instance.Foo(); // Compile sans erreur mais lève une exception à l’exécution puisque la méthode n’existe pas (RuntimeBinderException)
}
}
Il est possible de mixer typage dynamique et statique de manière transparente :
dynamic instance = new Sample();
string value = instance.Foo(); // Convertion implicite vers string
Les possibilités les plus intéressantes avec les types dynamic viennent avec l’interface IDynamicMetaObjectProvider et une de ses implémentations DynamicObject. Cette classe permet de remplacer les opérations telles qu’accéder ou assigner une valeur à un membre, appeler une méthode, ou encore effectuer une conversion de type. Pour cela plusieurs méthodes peuvent être overridées :
public class DynamicObject : IDynamicMetaObjectProvider
{
public virtual bool TryGetMember(GetMemberBinder binder, out object result)
public virtual bool TrySetMember(SetMemberBinder binder, object value)
public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
public virtual bool TryConvert(ConvertBinder binder, out object result)
public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result)
public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result)
public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
public virtual IEnumerable<string> GetDynamicMemberNames()
}
Pour notre cas d’usage, il suffit d’implémenter les différentes méthodes en utilisant la réflexion :
public class ReflectionDynamicObject : DynamicObject
{
private const BindingFlags DefaultBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private readonly object _originalObject;
readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();
readonly IDictionary<string, FieldInfo> _fields = new Dictionary<string, FieldInfo>();
public ReflectionDynamicObject(object obj)
{
if (obj == null) throw new ArgumentNullException("obj");
_originalObject = obj;
CreateMemberCache();
}
private void CreateMemberCache()
{
foreach (var propertyInfo in type.GetProperties(DefaultBindingFlags))
_properties.Add(propertyInfo.Name, propertyInfo);
foreach (var fieldInfo in type.GetFields(DefaultBindingFlags))
_fields.Add(fieldInfo.Name, fieldInfo);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string name = binder.Name;
PropertyInfo property;
if (_properties.TryGetValue(name, out property))
{
property.SetValue(_originalObject, value);
return true;
}
FieldInfo field;
if (_fields.TryGetValue(name, out field))
{
field.SetValue(_originalObject, value);
return true;
}
return false;
}
// Le code complet : https://gist.github.com/meziantou/d5078af67c3f4d3a1f20
}
Pour utiliser ce cette classe rien de plus simple :
class Sample
{
private string Name { get; set; }
}
static void Main(string[] args)
{
var sample = new Sample();
dynamic dynamicSample = new ReflectionDynamicObject(sample);
dynamicSample.Name = "John";
string name = dynamicSample.Test;
Console.WriteLine(name); // Affiche John
}
La réflexion est beaucoup plus simple avec un petit wrapper et le type dynamic :)