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