Gràce à Alex, nous pouvons maintenant écrire la ligne suivante pour récupérer City sans forcement tester la nullité des propriétés intermédiaires:
La méthode Maybe est une méthode d'extension qui se base sur une signature de délégué. La méthode Maybe2, quant à elle, va travailler sur l'arbre d'expressions de la lambda. La manipulation de l'arbre permet de reconstruire une série d'appels de la méthode Maybe en replaçant les appels aux membres en appel de Maybe. Le gros du travail est situé dans la méthode ConvertMemberToMethodCall:
public static class MaybeClass
{
public static V Maybe<T, V>(this T t, Func<T, V> selector)
where T : class
where V : class
{
if (t == null) return null;
return selector(t);
}
public static V Maybe2<T, V>(this T t, Expression<Func<T, V>> ex)
where T : class
where V : class
{
// On ne gère que le cas de la démo
if (ex.Body is MemberExpression)
{
MethodCallExpression memberEx = ConvertMemberToMethodCall(ex.Body as MemberExpression);
LambdaExpression lambda = Expression.Lambda(memberEx, new ParameterExpression[] { ex.Parameters[0] });
return lambda.Compile().DynamicInvoke(new object[] { t }) as V;
}
else
{
throw new NotSupportedException("");
}
}
/// <summary>
/// Cette méthode convertit un appel de membre en appel de méthode. En gros:
/// '.MaProp' devient '.Maybe(p => p.MaProp)'
/// </summary>
/// <param name="memberExpression"></param>
/// <returns></returns>
private static MethodCallExpression ConvertMemberToMethodCall(MemberExpression memberExpression)
{
Expression ex = null;
// L'appel récursif est réalisée ici
if (memberExpression.Expression is MemberExpression)
{
ex = ConvertMemberToMethodCall(memberExpression.Expression as MemberExpression);
}
else
{
ex = memberExpression.Expression;
}
// Un récupère la méthode Générique "Maybe"
MethodInfo methodInfo = typeof(MaybeClass).GetMethod("Maybe",BindingFlags.Public | BindingFlags.Static);
// Pour la démo, nous supposons que le membre est une propriété
PropertyInfo prop = memberExpression.Member as PropertyInfo;
if (prop != null)
{
// Passage obligatoire: récupèrer une version type de la méthode "Maybe"
methodInfo = methodInfo.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, prop.PropertyType });
}
else
{
throw new NotImplementedException("");
}
// Création d'un paramètre pour la lambda passé en paramètre de Maybe
ParameterExpression p = Expression.Parameter(memberExpression.Member.DeclaringType, "p");
// Création de la lambda
LambdaExpression maybeLamba = Expression.Lambda(
Expression.MakeMemberAccess(p, memberExpression.Member),
new ParameterExpression[] { p });
// Création de l'appel à Maybe
MethodCallExpression result = Expression.Call(
null,
methodInfo,
new Expression[] { ex, maybeLamba});
return result;
}
class Program
{
static void Main(string[] args)
{
Car car1 = new Car() { Owner = new Person() { Addr = new Address() { City = "Paris" } } };
Car car2 = new Car() { Owner = new Person() { Addr = new Address() } };
Car car3 = new Car() { Owner = new Person() };
Car car4 = new Car();
List<Car> carList = new List<Car> { car1, car2, car3, car4 };
foreach (var car in carList)
{
string city1 = car.Maybe2(p => p.Owner.Addr.City);
string city2 = car.Maybe(p => p.Owner).Maybe(p => p.Addr).Maybe(p => p.City);
Console.WriteLine("City: {0} - {1}", city1??"NULL",city2??"NULL");
}
Console.ReadLine();
}
}
Comme je disais au début, ce n'est par ce que l'on peut le faire que l'on doit le faire: ) Coté performance c'est forcément par bon du tout. Par contre ce genre de manipulation est un bon exemple de ce que les "expression trees" permettent de faire. Merci à Matthieu pour la relecture :)