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 :
- Utiliser les classes de l'espace de nom System.Reflection.Emit
- Utiliser les arbres d'expressions de LinQ
- Utiliser Mono.Cecil qui est un projet open source.
- Utiliser l'API bas niveau de PostSharp. C'est un framework de programmation orientée aspect.
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
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
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:
Enregistrer un commentaire