MetaTips

Le blog de l'edition logiciel en ligne

Articles

Expressions dynamiques compilees

Posted by Allouche Mathieu on December 4, 2009 at 10:10 AM

Vous devez souvent, par nécessité, laisser une bonne part de liberté aux personnes qui utilisent vos applications, notament lorsque certains critères évoluent au fils du temps. Par contraites techniques, ces critères ne peut pas être compilés, et doivent être placés dans des fichiers de configuration, qui deviennent assez rapidement complexes pour prendre un maximum de cas en charge. C'est la que les expressions dynamiques compilées rentre en jeu, afin de laisser à l'utilisateur le maximum de souplesse, sans sacrifier completement les performances .

Type : Article / Tutorial - Télécharger la source ici
Niveau : 3/5
Dur?e de lecture : env. 1 heure
Compatibilit? / s'applique ? :(

Il existe bien une multitude de cas, ou l'on est oblig? d'?valuer ? la vol? des expressions. Mais l'objectif de cet article n'est pas proposer un framework complet mais une solution simple qu'il sera possible de faire ?voluer.
Dans notre exemple nous aimerions pouvoir disposer d'un fichier de configuration contenant une expression complexe faisant rentrer en ligne de compte des variables internes ? notre traitement, ainsi que la possibilit? de tester la viabilit? de l'expression avant meme son interpretation.

Pour cela, nous allons utiliser 4 mecanismes :

  1. La g?n?ration dynamique de code avec un simple StringBuilder
  2. La compilation de classe ? la vol?e avec les classes CodeDomProvider, CompilerParameters, CompilerResults
  3. Réflexion pour l'instanciation de la classe ainsi générée avec Activator
  4. Les Generics pour traiter n'importe quels types de retours.

Voici un exemple de fichier de configuration :

xml version="1.0"encoding="utf-8" ?>

configuration>

?????appSettings>

???????????add key="SendAlertExpression" value="(SrcNbLines > 2 || Level > 10)"/>

????? appSettings>

configuration>

Voici un exemple de code d'utilisation :

ExpressionParserbool> Exp1 = new ExpressionParserbool>('1==1');

if (Exp1.Compile()) Console.WriteLine(Exp1.Value + ' = ' + Exp1.Eval());

?

ExpressionParserint> Exp2 = new ExpressionParserint>('1+1');

if (Exp2.Compile()) Console.WriteLine(Exp2.Value + ' = ' + Exp2.Eval());

?

string SendAlertExpression = System.Configuration.ConfigurationManager.AppSettings.Get('SendAlertExpression');

ExpressionParserbool> Exp3 = new ExpressionParserbool>(SendAlertExpression);

Exp3.AddVarint>('SrcNbLines');

Exp3.AddVardouble>('Level');

if (Exp3.Compile())

{

???? Exp3.SetVarint>('SrcNbLines', 2000);

???? Exp3.SetVardouble>('Level',20);

???? Console.WriteLine(Exp3.Value + ' = ' +Exp3.Eval());

}}

Afin de maximiser les perfomances de l'appel ? la methode Eval(), il convient de ne pas utiliser les mecanismes de reflexion. Nous allons pour cela utiliser une interface, qui jouera le role d'abstraction des classes g?n?r?es :

namespace TestApplication

??? public interface Evaluable

??? {

???????? T Eval();

??? }

}

Nous allons maintenant ?crire une classe, qui va g?n?rer dynamique le code. Nous nommerons ExpressionParser . Afin de gagner des performances, vous remarquerez bien que celle-ci poss?de une donn?e membre de type Evaluable

namespace TestApplication

{

??? public class ExpressionParser

??? {

??????? #region FIELDS

??????? private Evaluable _InternalParser = null;

??????? private string _Expression = null;

??????? private StringBuilder _Vars = null;

??????? private Dictionarystring, PropertyInfo> _InternalProperties = new Dictionarystring, PropertyInfo>();

??????? #endregion

?

??????? #region PROPERTIES

??????? public string Value { get { return _Expression; } }

??????? #endregion

?

??????? #region CONSTRUCTOR

??????? public ExpressionParser(string expression)

??????? {

??????????? _Expression = expression;

??????????? _Vars = new StringBuilder();

??????? }

??????? #endregion

?

??????? #region METHODS

??????? public bool Compile()??????????????????????????????????????

??????? {

??????????? //Compiler declaration

??????????? CodeDomProvider cp = CodeDomProvider.CreateProvider('CSharp');

??????????? CompilerParameters cpar = new CompilerParameters();

??????????? cpar.GenerateInMemory = true;

??????????? cpar.GenerateExecutable = false;

?????? ?????cpar.ReferencedAssemblies.Add('system.dll');

??????????? cpar.ReferencedAssemblies.Add('TestApplication.exe');

?

??????????? //Dynamic class

??????????? StringBuilder SourceCode = new StringBuilder();

??????????? SourceCode.Append('using System;');

??????????? SourceCode.Append('class DedicatedParser: TestApplication.Evaluable + typeof(T).Name + '>');

??????????? SourceCode.Append('{ public DedicatedParser(){}');

??????????? SourceCode.Append(_Vars.ToString());

??????????? SourceCode.Append('public ' + typeof(T).Name + ' Eval()');

??????????? SourceCode.Append('{');

??????????? SourceCode.Append('return ');

??????????? SourceCode.Append(_Expression);

??????????? SourceCode.Append(';}}');

?

??????????? //Compilation

??????????? CompilerResults cr = cp.CompileAssemblyFromSource(cpar, SourceCode.ToString());

??????????? foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors) Console.WriteLine(ce.ErrorText);

??????????? if (cr.Errors.Count == 0 && cr.CompiledAssembly != null)

??????????? {

??????????????? Type InternalParserType = cr.CompiledAssembly.GetType('DedicatedParser');

??????????????? try

??????????????? {

??????????????????? if (InternalParserType != null)

??????????????????? {

??????????????????????? _InternalParser = (Parser;)Activator.CreateInstance(InternalParserType);

??????????????????????? var Props = InternalParserType.GetProperties();

??????????????????????? foreach (var ii in Props)

??????????????????????? {

??????????????????????????? _InternalProperties.Add(ii.Name, ii);

??????????????????????? }

??????????????????? }

??????????????? }

??????????????? catch (Exception ex)

??????????????? {

??????????????????? Console.WriteLine(ex.Message);

??????????????? }

??????????????? return true;

??????????? }

??????????? else

??????????????? return false;

??????? }

??????? public T Eval()????????????????????????????????????????????

??????? {

??????????? if (_InternalParser == null) throw new Exception('Compilation exception');

??????????? return _InternalParser.Eval();

??????? }

??????? public void AddVar(string varName)????????????????

??????? {

??????????? _Vars.Append(' private ' + typeof(TypeVar).Name + ' _' + varName + ';');

??????????? _Vars.Append(' public ' + typeof(TypeVar).Name + ' ' + varName + ' { get { return _' + varName + '; }? set { _' + varName + ' = value; }}');

??????? }

??????? public void SetVar(string varName, TypeVar value)?

??????? {

??????????? try

??????????? {

??????????????? if (_InternalParser == null) throw new Exception('Compilation exception.');

??????????????? _InternalProperties[varName].SetValue(_InternalParser, value, null);

??????????? }

??????????? catch

??????????? {

??????????????? throw new Exception('Unknonw property or type.');

??????????? }

?

??????? }

??????? #endregion

??? }

}

?

Categories: Developpements

Post a Comment

Oops!

Oops, you forgot something.

Oops!

The words you entered did not match the given text. Please try again.

Already a member? Sign In

0 Comments