La question du calcul de formules stockées dans une chaine est récurrente sur le forum.

Je vous propose ici une classe simple, permettant d'évaluer la valeur de formules numériques simples, en utilisant la compilation dynamique via "CodeDom".

La formule peut utiliser les quatre opérations de "base", le modulo, en fait, l'ensemble des opérateur du langage C# (pour faire court), ainsi que n'importe laquelle des méthodes statiques ou des constantes (pi, e) de la classe Math (sans avoir besoin de préfixer la méthode ou la constante - en cela la classe que je poste ici est plus complète que l'exemple que j'avais posté par ailleurs sur le forum). Les méthodes et constantes sont case-insensitive.

Il est possible d'ajouter une autre classe de méthodes statiques très simplement dans le membre :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
private Type[] _classToParse = { typeof( Math ) };
De la même façon, des références à des assemblies externes peuvent être ajoutées dans le membre :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
private string[] _referencesToAdd =  { "System.DLL" };
ainsi que les namespaces associés dans

Code : Sélectionner tout - Visualiser dans une fenêtre à part
private string[] _nameSpacesToAdd =  { "System" };
Bien entendu, cette classe est un simple squelette, bien insuffisant pour de vrais calculs car elle retourne toujours un simple "double" et ne sait pas travailler sur des vecteurs par exemple.

Voici donc le code

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.Reflection;
using System.CodeDom;
using System.Collections.ObjectModel;
 
namespace TestFormulaViaCodeDom
{
    public class DynamicFormula
    {
        private string _formula;
        private List<string> _compilerMessages = new List<string>();
        private CompilerErrorCollection _compilerErrors;
 
        private string _language = "CSharp";
        private readonly string _nameSpace;
        private const string _className = "formula";
        private const string _methodName = "compute";
        private int _warningLevel = 1;
        private Type[] _classToParse = { typeof( Math ) };
        private string[] _nameSpacesToAdd =  { "System" };
        private string[] _referencesToAdd =  { "System.DLL" };
 
 
        public DynamicFormula( string formula )
        {
            _formula = formula.Trim();
            // ensure unique namespace to avoid potential conflict in app domain
            _nameSpace= "Dyngen_" + Guid.NewGuid().ToString().Replace('-','_'); // "-" char is invalid in names
        }
 
        /// <summary>
        /// True if compilation fails (must be checked if Compute returns NaN).
        /// </summary>
        public bool HasCompilationFailed
        {
            get
            {
                return _compilerErrors.Count > 0;
            }
        }
 
        /// <summary>
        /// Return compile errors
        /// </summary>
        public CompilerErrorCollection CompilerErrors
        {
            get
            {
                return _compilerErrors;
            }
        }
 
        /// <summary>
        /// Returns compiler messages (informations and errors)
        /// </summary>
        public ReadOnlyCollection<string> CompilerMessages
        {
            get
            {
                return _compilerMessages.AsReadOnly();
            }
        }
 
        /// <summary>
        /// Compute the formula passed to the class .ctor
        /// </summary>
        /// <returns></returns>
        public double Compute()
        {
            double formulaResult = double.NaN;
            string parsedFormula = ParseForSymbolClasses( _formula );
            CodeCompileUnit unit = prepareCompileUnit( parsedFormula );
            Assembly dynamicAssembly = compileCode( unit, _language );
            if (dynamicAssembly != null)
            {
                object formulaComputer = dynamicAssembly.CreateInstance(_nameSpace + "." + _className, true );
                MethodInfo computeFormula = formulaComputer.GetType().GetMethod( _methodName );
                formulaResult = (double)computeFormula.Invoke( formulaComputer, null );
            }
            return formulaResult;
        }
 
        private string ParseForSymbolClasses( string formula )
        {
            string parsedFormula = formula;
            foreach (Type symbolClass in _classToParse)
            {
                parsedFormula = parseAndReplaceForSymbols( symbolClass, parsedFormula );
            }
            return parsedFormula;
        }
 
        /// <summary>
        /// Parse the formula to prefix  static methods (e.g. Cos, Sin, ... for Math class) and constants(PI, E) by class name
        /// </summary>
        /// <param name="formula"></param>
        /// <returns></returns>
        private string parseAndReplaceForSymbols(Type symbolClass, string formula)
        {
            int lastPos;
            int pos;
            MemberInfo[] mathSymbols = symbolClass.GetMembers( BindingFlags.Public | BindingFlags.Static );
            foreach (MemberInfo mathSymbol in mathSymbols)
            {
                pos = 0;
                lastPos = 0;
                while (pos > -1  && lastPos < formula.Length - 1)
                {
                    pos = formula.IndexOf( mathSymbol.Name, lastPos, StringComparison.InvariantCultureIgnoreCase );
                    // check if we have found a complete word (e.g not Tan in ATan for instance)
                    if (pos > -1)
                    {
                        char previousChar = '0';
                        bool usePrevChar = false;
                        char nextChar = '0';
                        bool useNextChar = false;
                        if (pos > 0)
                        {
                            previousChar = formula[pos - 1];
                            usePrevChar = true;
                        }
                        if (pos + mathSymbol.Name.Length  < formula.Length - 1)
                        {
                            nextChar = formula[pos + mathSymbol.Name.Length];
                            useNextChar = true;
                        }
                        if ((useNextChar && (char.IsLetterOrDigit( nextChar ) == false)) || (useNextChar == false))
                        {
                            if ((usePrevChar && (char.IsLetterOrDigit( previousChar ) == false)) || (usePrevChar == false))
                            {
                                // it's a complete word --> replace SymbolName by ClassName.SymbolName and set the correct case sensitivity (remove and insert)
                                formula = formula.Remove( pos, mathSymbol.Name.Length ).Insert( pos, symbolClass.Name + "." + mathSymbol.Name );
                            }
                        }
                        lastPos = pos + mathSymbol.Name.Length + symbolClass.Name.Length + 1;
                    }
                }
            }
            return formula;
        }
 
        private Assembly compileCode( CodeCompileUnit compileunit, string language )
        {
            CompilerParameters compilerParameters = new CompilerParameters();
            foreach (string referenceToAdd in _referencesToAdd)
            {
                compilerParameters.ReferencedAssemblies.Add( referenceToAdd );
            }
            compilerParameters.GenerateExecutable = false;
            compilerParameters.GenerateInMemory = true;
            compilerParameters.IncludeDebugInformation = false;
            compilerParameters.WarningLevel = _warningLevel;
 
            // Invoke compilation.
            CodeDomProvider provider = CodeDomProvider.CreateProvider( language );
            CompilerResults compilerResults = provider.CompileAssemblyFromDom( compilerParameters, compileunit );
            _compilerMessages.Clear();
 
            foreach (String compilerMessage in compilerResults.Output)
            {
                if (String.IsNullOrEmpty( compilerMessage.Trim() ) == false)
                {
                    _compilerMessages.Add( compilerMessage );
                }
            }
 
            // Return the results of compilation.
            _compilerErrors = compilerResults.Errors;
            return (_compilerErrors.Count == 0) ? compilerResults.CompiledAssembly : null;
        }
 
        private CodeCompileUnit prepareCompileUnit( string formulaString )
        {
            CodeNamespace compute = new CodeNamespace( _nameSpace);
            foreach (string nsToAdd in _nameSpacesToAdd)
            {
                compute.Imports.Add( new CodeNamespaceImport( nsToAdd ) );
            }
 
            CodeCompileUnit compileUnit = new CodeCompileUnit();
            compileUnit.Namespaces.Add( compute );
            CodeTypeDeclaration formulaComputing = new CodeTypeDeclaration(_className );
            compute.Types.Add( formulaComputing );
            CodeMemberMethod computeFormulaCode = new CodeMemberMethod();
            computeFormulaCode.Attributes = MemberAttributes.Public;
            computeFormulaCode.Name = _methodName;
            computeFormulaCode.ReturnType = new CodeTypeReference( typeof( double ) );
            CodeSnippetExpression formula = new CodeSnippetExpression( formulaString );
            CodeMethodReturnStatement computeFormulaReturnStatement = new CodeMethodReturnStatement( formula );
            computeFormulaCode.Statements.Add( computeFormulaReturnStatement );
 
            formulaComputing.Members.Add( computeFormulaCode );
            return compileUnit;
        }
    }
}