mercredi 17 octobre 2007

Evaluation dynamique d'expressions

Article: Evaluation dynamique d'expressions en C#
 
Langage: C#
Environnement: Microsoft Visual Studio 2005
Description: Ce petit article présente comment réaliser en quelques lignes un analyseur d'expressions mathématiques, en utilisant la compilation dynamique.
 
Premier point: L'idée
Prenons le code suivant:
public Double Run()
{
  return 1+1+1;
}
Que va-t'on obtenir en appelant Run? Réponse: 3, bien évidement. Par contre, c'est purement statique.
Supposons maintenant une string contenant le texte suivant:
string _source =
@"using System;
public class Interpreter{{
public static Double Run(){{ return {0}; }}
}}";
 
Les doubles accolades sont là pour gérer le {0}, paramètre de formatage. Ainsi, on a dans notre string le code d'une micro-classe avec sa méthode, mais pas le fameux 1+1+1. Pour fixer une expression dans notre micro-classe, il fudrait appeller String.Format(_source, "1+1+1");
L'idée est donc de créer à la volée le code d'une micro-classe, puis de le compiler et de l'executer.
 
Deuxième point: Réalisation
Créaons un projet Windows application quelconque, avec une TextBox tbSource et un bouton btnGo.
Dans notre classe, déclarer les usings suivants:
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
 
 
Déclarer aussi _source:
private string _source =
@"using System;
public class Interpreter{{
public static Double Run(){{ return {0}; }}
}}";
 
 
Puis, sur le btnGo_Click, mettre le code suivant:
CSharpCodeProvider codeprovider = new CSharpCodeProvider ();
CompilerResults results = codeprovider.CompileAssemblyFromSource(new CompilerParameters(), String.Format(_source, tbSource.Text));
if (results.Errors.Count != 0) MessageBox.Show("Error" );
Type mainType = results.CompiledAssembly.GetType("Interpreter");
MethodInfo mainMethod = mainType.GetMethod("Run");
MessageBox.Show(mainMethod.Invoke(null, null ).ToString());
 
 
Et voilà, six lignes et c'est réglé !
Explications: le CSharpCodeProvider fournit tout un tas de méthode utilisables pour compiler du code C# dans une Assembly créée dynamiquement (par exemple en utilisant CompileAssemblyFromSource).
Ensuite, GetType("Interpreter") sur l'assembly générée nous donne notre nouveau type correspondant à notre nouvelle classe Interpreter, et GetMethod("Run") permet d'en récuperer l'unique méthode.
Enfin, Invoke sert à invoquer la méthode. Etant donnée que la méthode Run est statique et sans paramètre, alors les deux paramètres d'Invoke sont null (le premier est l'instance d'Interpreter à utiliser pour appeler Run, le second la liste des paramètres).
 
 
Dernier point: On teste
Lancons le programme. 1+1+1, réponse 3, ca passe.
Plus dur (12*5) + (9*8)/12, réponse 66, ca passe.
Encore plus dur, Math.Cos(Math.PI/3), réponse 0.5. Nickel
 
Et voilà, après c'est sûr que cette application n'a pas grand interêt mais cela montre que l'utilisation du CSharpCodeProvider et de la compilation dynamique sont assez simples, à vous de trouver les applications qui vont bien avec.