Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Vendredi c'est Expression Tree :)

Que les puristes de la performance soient rassurés, le reste du post n'est que pour le fun :) En discutant avec Matthieu, du post d'Alex James et du sien, j'ai voulu aller un peu plus loin.

Retour en arrière. Voici mes 3 classes:

public class Person

{

    public Address Addr { get; set; }

}

 

public class Address

{

    public string City { get; set; }

}

 

public class Car

{

    public Person Owner {get;set;}

}

 

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:

 

string city = car.Maybe(p => p.Owner).Maybe(p => p.Addr).Maybe(p => p.City);

 

Mon idée est de vouloir directement écrire:

 

string city = car.Maybe2(p => p.Owner.Addr.City);

 

 

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;

        }   

 

 

Nous pouvons tester cela de cette façon:

 

   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();

        }

    }

 

Et l'on obtient le résultat suivant:

City: Paris - Paris
City: NULL - NULL
City: NULL - NULL
City: NULL - NULL

 

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  :)

 

 

Publié vendredi 29 février 2008 16:18 par Miiitch
Classé sous : ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: Vendredi c'est Expression Tree :)

t'es un grand malade :D

vendredi 29 février 2008 16:33 by sebmafate

# re: Vendredi c'est Expression Tree :)

Super exemple de meta-programmation.

Attention, parfois les instructions cast non visibles viennent d'insérer dans l'arbre d'expression. Mieux vaut utiliser un visiteur qui offre une analyse plus robuste.

Voir l'appendix ici: http://blogs.msdn.com/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx

Mitsu

vendredi 29 février 2008 16:46 by Mitsu

# re: Vendredi c'est Expression Tree :)

"Vendredi c'est Expression Tree :)" ou comment perdre tous les deux la moitié de l'après-midi ;)

N'empêche que ça valait le coup. Franchement, c'est trop fort les Expression Tree.

vendredi 29 février 2008 16:47 by Matthieu MEZIL

# re: Vendredi c'est Expression Tree :)

je me suis arreté à ca :

string city0 = ((Expression

<Func><String>&gt;)(() =&gt; car.Owner.Addr.City)).Maybe3();

avec Maybe3 qui découle de source et une modification au niveau de ConvertMemberToCall

// Pour la démo, nous supposons que le membre est une propriété

MemberInfo prop = memberExpression.Member as MemberInfo;

if (prop != null)

{

   // Passage obligatoire: récupèrer une version type de la méthode "Maybe"

   if (prop is PropertyInfo)

       methodInfo = methodInfo.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, ((PropertyInfo)prop).PropertyType });

   else if (prop is FieldInfo)

       methodInfo = methodInfo.MakeGenericMethod(new Type[] { memberExpression.Member.DeclaringType, ((FieldInfo)prop).FieldType  });

}

L'idée était de permettre ca : string city0 = (Null<String>&gt;)(() =&gt; car.Owner.Addr.City);

mais Expression est sealed :( on ne peut donc avoir ce type :

public class Null<T> :

   Expression<Func><T>&gt;

   where T : class

{

   public static implicit operator T(Null<T> nullExpression)

   {

       return MaybeClass.Maybe3(nullExpression);

   }

}

dommage ca aurait été marrant :)

samedi 1 mars 2008 01:23 by cyril

# re: Vendredi c'est Expression Tree :)

C'est bien les ET hein, vivement qu'il n'y ai plus seulement que les expressions, mais aussi les statements.

En attendant, si je peux me permettre, moi j'utiliserais la factory methode:

Expression.Lambda

<T> au lieu de Lambda tout court, ça permet de rester typé tout le temps, et plus besoin du dynamic invoke.

samedi 1 mars 2008 09:17 by Jb Evain
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Etes-vous yOS compatible ? (2/3) : la nouvelle plateforme Yammer–Office 365–SharePoint par Le blog de Patrick [MVP SharePoint] le 04-22-2014, 09:27

- [ #Yammer ] [ #Office365 ] Quelques précisions sur l’activation de Yammer Entreprise par Le blog de Patrick [MVP SharePoint] le 04-22-2014, 09:03

- Après Montréal, ce sera Barcelone, rendez-vous à la European SharePoint Conference 2014 ! par Le blog de Patrick [MVP SharePoint] le 04-19-2014, 09:21

- Emportez votre sélection de la MSDN dans la poche ? par Blog de Jérémy Jeanson le 04-17-2014, 22:24

- [ #Office365 ] Pb de connexion du flux Yammer ajouté à un site SharePoint par Le blog de Patrick [MVP SharePoint] le 04-17-2014, 17:03

- NFluent & Data Annotations : coder ses propres assertions par Fathi Bellahcene le 04-17-2014, 16:54

- Installer un site ASP.net 32bits sur un serveur exécutant SharePoint 2013 par Blog de Jérémy Jeanson le 04-17-2014, 06:34

- [ SharePoint Summit Montréal 2014 ] Tests de montée en charge SharePoint par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 20:44

- [ SharePoint Summit Montréal 2014 ] Bâtir un site web public avec Office 365 par Le blog de Patrick [MVP SharePoint] le 04-16-2014, 18:30

- Kinect + Speech Recognition + Eedomus = Dommy par Aurélien GALTIER le 04-16-2014, 17:17