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;
}
}
} |
Partager