22 janvier 2009

Différentes façons de créer dynamiquement une méthode (1/3)

Dernièrement, j'avais cherché à créer une méthode grâce à l'API CodeDom, de générer/compiler un assemblage, de le charger en mémoire et enfin d'exécuter la méthode générée.

Cette méthode fonctionne, cependant elle a l'inconvénient de passer par une compilation du C# en MSIL avant de pouvoir enfin exécuter la méthode générée.
Je me suis demandé alors si c'était possible de générer et d'injecter dynamiquement une méthode dans l'assemblage utilisé ou ailleurs et de l'exécuter par la suite. En cherchant sur la toile, je suis tombé sur 4 solutions abordables :

J'ai donc mis en œuvre les 3 premières solutions, c'est-à-dire une implémentation avec System.Reflection.Emit, les arbres d'expressions et Mono.Cecil. Concernant PostSharp, je pense que je m'y pencherai un autre jour.

Pour information, chaque méthode générée est un simple Hello World affichée dans la console.

Avec les arbres d'expressions

Commençons par l'implémentation la plus facile, celle utilisant les arbres d'expressions. Succinctement, un arbre d'expressions est une structure de données représentant du code. Il est ensuite possible de générer une méthode dynamiquement. Dans LinQ , les arbres d'expressions sont utilisés avec l'interface IQueryable et le type générique Expression. Ce dernier est toujours associé à des expressions lambdas, qui ne sont rien d'autres que des méthodes anonymes composée d'une seule instruction (comprenez un seul point-virgule ;). Ensuite le compilateur C# ou VB.Net transforme l'expression lambda en un arbre d'expression.

Pour information, LinQ to SQL et LinQ to Entities utilisent les arbres d'expressions pour transformer une expression lambda (condition dans un méthode Where par exemple) en SQL.

L'instruction suivante :

Expression<Action> expr = () => Console.WriteLine("Hello World avec les arbres d'expression !");

Donne après décompilation avec Reflector :

Expression<Action> expr = Expression.Lambda<Action>(
                                    Expression.Call(null, 
                                                    typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) }), 
                                                    new Expression[] { Expression.Constant("Hello World avec les arbres d'expression !", typeof(string)) }), 
                                    new ParameterExpression[0]);


On s'aperçoit que le compilateur C# a transformé notre expression lambda en un arbre d'expressions.
Pour plus de détails sur les arbres d'expressions, voici le lien MSDN.

Voici donc le code pour générer notre HelloWorld de façon dynamique et avec les arbres d'expression :


Expression<Action> expr = Expression.Lambda<Action>(
                                    Expression.Call(null, 
                                                    typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) }), 
                                                    new Expression[] { Expression.Constant("Hello World avec les arbres d'expression !", typeof(string)) }), 
                                    new ParameterExpression[0]);
var meth2 = expr.Compile();
meth2();


A la 1ère ligne, l'expression lambda est définie et assignée au type Expression. Ce code une fois compilé résulte donc en un arbre d'expressions. En d'autres termes, Le compilateur C# a transformé la logique de l'expression lambda en données. Pour transformer ensuite l'arbre d'expressions en logique, il suffit simplement d'appeler la méthode Compile de la classe Expression . Cette méthode retourne un délégué qui représente la méthode générée.

Petite remarque, il est très facile de générer du code via les arbres d'expressions. Cependant, un arbre ne peut contenir que l'équivalent d'une instruction, tout comme les expressions lambdas. Du coup, pas besoin de connaitre le MSIL.

Nous verrons dans le prochain billet comment générer une méthode avec les classes de l'espace de nom System.Reflection.Emit.

Pour plus d'informations, l'archive ci-dessous reprend le code des 3 méthodes de génération dynamique présentées.

Aucun commentaire: