Einleitung
Das .NET-Framework bietet über das CodeDomProvider-Objekt eine Möglichkeit zur Laufzeit Sourcecode zu kompilieren und diesen auszuführen. Es kann somit auf elegante Art und Weise beliebiger Code, u.A. mathematische Ausdrücke im C#-Syntax, evaluiert werden. Die Klasse CCodeCompiler kompiliert eine zur Laufzeit in C# „geschriebene“ Funktion (sbCode) und gibt deren Referenz für die Ausführung zurück. Die genaue Anwendung ist im unten stehenden Anwendungsbeispiel beschrieben.
Code
using Microsoft.CSharp; using System.Reflection; using System.CodeDom.Compiler; /// <summary> /// helper class for compiling C#-code at runtime /// free code (W) 2010 by admin of codezentrale.de /// </summary> public class CCodeCompiler { private Assembly _assCompiledAssembly = null; private MethodInfo _miMethodInfo = null; private Type _tyContainerType = null; private bool _bCompiled = false; private string _sClassName = string.Empty; private CompilerErrorCollection _cecCompilerErrors = null; /// <summary> /// reference to the compiled assembly /// </summary> public Assembly CompiledAssembly { get { return CompiledAssembly; } } /// <summary> /// reference to the compiled method /// </summary> public MethodInfo MethodInfo { get { return _miMethodInfo; } } /// <summary> /// type of the compiled method /// </summary> public Type ContainerType { get { return _tyContainerType; } } /// <summary> /// flag, if compiling was successfully /// </summary> public bool IsCompiled { get { return _bCompiled; } } /// <summary> /// classname of the compiled method /// </summary> public string ClassName { get { return _sClassName; } } /// <summary> /// a string with all the errors stored in the CompilerErrorCollection /// </summary> public string CompilerErrors { get { string sRetVal = string.Empty; if (_cecCompilerErrors != null) { foreach (CompilerError ce in _cecCompilerErrors) { sRetVal += ce.ToString() + Environment.NewLine; } } return sRetVal; } } /// <summary> /// creates a virtual method from given sourcecode /// </summary> /// <param name="sSourceCode">the complete sourcecode of the method</param> /// <param name="sMethodName">the name of the method</param> /// <param name="bIncludeDebugInfo">include debuginfo into the compiled assembly</param> /// <param name="psaReferences">some referenced assmblies (.NET DLLs) beiing used for working the code, full pathname needed</param> /// <returns>true if the compilation was successfull, else false</returns> public bool CreateMethodRef(string sSourceCode, string sMethodName, bool bIncludeDebugInfo, params string[] psaReferences) { _assCompiledAssembly = null; _miMethodInfo = null; _tyContainerType = null; _bCompiled = false; _sClassName = sMethodName + "Container"; _cecCompilerErrors = null; // the compiler object CodeDomProvider cdpCompilerObject = CodeDomProvider.CreateProvider("CSharp"); // build options CompilerParameters cpParams = new CompilerParameters(); cpParams.GenerateInMemory = true; cpParams.GenerateExecutable = false; cpParams.IncludeDebugInformation = bIncludeDebugInfo; cpParams.ReferencedAssemblies.AddRange(psaReferences); // string for the used namespace string sNameSpace = cpParams.OutputAssembly; if (sNameSpace == null) sNameSpace = "Evaluated"; // build the complete code StringBuilder sbCode = new StringBuilder(); // include the needed assemblies foreach (string sAssemblies in cpParams.ReferencedAssemblies) { sbCode.Append("using " + Assembly.LoadFrom(sAssemblies).GetName().Name + ";" + Environment.NewLine); } // include namespace sbCode.Append("namespace " + sNameSpace + Environment.NewLine); sbCode.Append("{" + Environment.NewLine); // include the class sbCode.Append(" public class " + _sClassName + Environment.NewLine); sbCode.Append(" {" + Environment.NewLine); // include the given sourcecode-part sbCode.Append(sSourceCode); sbCode.Append(" }" + Environment.NewLine); sbCode.Append("}" + Environment.NewLine); // compile the sourcecode CompilerResults crResults = cdpCompilerObject.CompileAssemblyFromSource(cpParams, sbCode.ToString()); // some errors? if (crResults.Errors.HasErrors) { _cecCompilerErrors = crResults.Errors; } else { // when everything was fine, set some variables // assembly _assCompiledAssembly = crResults.CompiledAssembly; // type of method _tyContainerType = _assCompiledAssembly.GetType(sNameSpace + "." + _sClassName, true); // the method _miMethodInfo = _tyContainerType.GetMethod(sMethodName); _bCompiled = true; } return _bCompiled; } /// <summary> /// creates a virtual method from given sourcecode /// </summary> /// <param name="sSourceCode">the complete sourcecode of the method</param> /// <param name="sNameSpace">a namespace where the source is compiled</param> /// <param name="sMethodName">the name of the method</param> /// <param name="bIncludeDebugInfo">include debuginfo into the compiled assembly</param> /// <returns>true if the compilation was successfull, else false</returns> public bool CreateMethodSimple(string sSourceCode, string sNameSpace, string sMethodName, bool bIncludeDebugInfo) { _assCompiledAssembly = null; _miMethodInfo = null; _tyContainerType = null; _bCompiled = false; _sClassName = sMethodName + "Container"; _cecCompilerErrors = null; // the compiler object CodeDomProvider cdpCompilerObject = CodeDomProvider.CreateProvider("CSharp"); // build options CompilerParameters cpParams = new CompilerParameters(); cpParams.GenerateInMemory = true; cpParams.GenerateExecutable = false; cpParams.IncludeDebugInformation = bIncludeDebugInfo; // add (reference) all global available assemblies foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { cpParams.ReferencedAssemblies.Add(asm.Location); } // compile the sourcecode CompilerResults crResults = cdpCompilerObject.CompileAssemblyFromSource(cpParams, sSourceCode); // some errors? if (crResults.Errors.HasErrors) { _cecCompilerErrors = crResults.Errors; } else { // when everything was fine, set some variables // assembly _assCompiledAssembly = crResults.CompiledAssembly; // type of method _tyContainerType = _assCompiledAssembly.GetType(sNameSpace + "." + _sClassName, true); // the method _miMethodInfo = _tyContainerType.GetMethod(sMethodName); _bCompiled = true; } return _bCompiled; } }
Anwendungsbeispiel
Nachfolgend sind zwei Beispiele für die Anwendung von CCodeCompiler aufgezeigt. Folgender C#-Code wird dabei zur Laufzeit kompiliert und ausgeführt:
using System; namespace Evaluated { public class CalcContainer { public double Calc(double[] x) { double dResult = 0.0; dResult = x[0] + x[1]; return dResult; } } }
Im Beispiel 1 werden explizit die zu referenzierenden Assemblies mit vollständigem Pfad und Namen angegeben, im Beispiel 2 dagegen intern von der Funktion alle verfügbaren Assemblies automatisch eingeladen.
using Microsoft.CSharp; using System.Reflection; using System.CodeDom.Compiler; // example 1 private void btnEvaluateRef_Click(object sender, System.EventArgs e) { // the methodname string sMethodName = "Calc"; // example code StringBuilder sbCode = new StringBuilder(); sbCode.Append(" public double " + sMethodName + "(double[] x)" + Environment.NewLine); sbCode.Append(" {" + Environment.NewLine); sbCode.Append(" double dResult = 0.0;" + Environment.NewLine); sbCode.Append(" dResult = x[0] + x[1];" + Environment.NewLine); sbCode.Append(" return dResult;" + Environment.NewLine); sbCode.Append(" }" + Environment.NewLine); // reference one needed assembly for "Math" called "System" string[] saReferenceAssemblyDLLs = new string[1]; saReferenceAssemblyDLLs[0] = @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll"; // for "using System;" try { // create a codecompiler object CCodeCompiler rtc = new CCodeCompiler(); // compile and create the Calc-Method if (rtc.CreateMethodRef(sbCode.ToString(), sMethodName, false, saReferenceAssemblyDLLs)) { // reference to the method MethodInfo miMethod = rtc.MethodInfo; // some input values for test calculation double[] daX = new double[2]; daX[0] = 1.2; daX[1] = 2.0; // call the compiled method with input values string sResult = miMethod.Invoke(Activator.CreateInstance(rtc.ContainerType), new object[] { daX } ).ToString(); } else { // show compiler errors MessageBox.Show(rtc.CompilerErrors); } } catch (Exception ce) { // if there is a exception during execution, show it MessageBox.Show(ce.Message + Environment.NewLine + ce.StackTrace); } } // example 2 private void btnEvaluateSimple_Click(object sender, System.EventArgs e) { // the namespace string sNameSpace = "Evaluated"; // the methodname string sMethodName = "Calc"; // example code StringBuilder sbCode = new StringBuilder(); // some assembly references sbCode.Append("using System;" + Environment.NewLine); // include namespace sbCode.Append("namespace " + sNameSpace + Environment.NewLine); sbCode.Append("{" + Environment.NewLine); // include the class sbCode.Append(" public class " + sMethodName + "Container" + Environment.NewLine); sbCode.Append(" {" + Environment.NewLine); sbCode.Append(" public double " + sMethodName + "(double[] x)" + Environment.NewLine); sbCode.Append(" {" + Environment.NewLine); sbCode.Append(" double dResult = 0.0;" + Environment.NewLine); sbCode.Append(" dResult = x[0] + x[1];" + Environment.NewLine); sbCode.Append(" return dResult;" + Environment.NewLine); sbCode.Append(" }" + Environment.NewLine); sbCode.Append(" }" + Environment.NewLine); sbCode.Append("}" + Environment.NewLine); try { // create a codecompiler object CCodeCompiler rtc = new CCodeCompiler(); // compile and create the Calc-Method without any assembly // these will be referenced all internally if (rtc.CreateMethodSimple(sbCode.ToString(), sNameSpace, sMethodName, false)) { // reference to the method MethodInfo miMethod = rtc.MethodInfo; // some input values for test calculation double[] daX = new double[2]; daX[0] = 1.2; daX[1] = 2.0; // call the compiled method with input values string sResult = miMethod.Invoke(Activator.CreateInstance(rtc.ContainerType), new object[] { daX } ).ToString(); } else { // show compiler errors MessageBox.Show(rtc.CompilerErrors); } } catch (Exception ce) { // if there is a exception during execution, show it MessageBox.Show(ce.Message + Environment.NewLine + ce.StackTrace); } }
- Weiterführender Link: Runtime Compilation (A .NET eval statement)