Introduction
In some cases it is very useful to compile the source code during execution. For this situation you can use the namespace Microsoft.CSharp
. This namespace contains the class CSharpCodeProvider
. Using the CSharpCodeProvider
you can have access to the C#-compiler. Please note that you can use the compiler with VB.NET also, just use the namespace Microsoft.VisualBasic
and the class VBCodeProvider
. In Addition to be able to compile your code on the fly you have to add the System.CodeDom
and the System.CodeDom.Compiler
namespaces.
First step
In this example we will use a RichTextBox
in which we'll load some example-code. After that you will be able to compile the code. In this step we'll have a closer look at the source code of the application:
CSharpCodeProvider csp = new CSharpCodeProvider();
ICodeCompiler cc = csp.CreateCompiler();
Using CSharpCodeProvider
we gain access to the compiler. Using this compiler we are able to use the csc.exe without any need for the command line. To compile the example-code successfully we have to set some compiler parameters. They include which assemblies should be referred to, where the output assembly has to be stored, which warning level we want to use and some other stuff. The compiler options that we have set are the same as the compiler options you have set using the csc.exe:
CompilerParameters cp = new CompilerParameters();
cp.OutputAssembly = Application.StartupPath + "\\TestClass.dll";
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");
cp.ReferencedAssemblies.Add("CSharpScripter.exe");
cp.WarningLevel = 3;
cp.CompilerOptions = "/target:library /optimize";
cp.GenerateExecutable = false;
cp.GenerateInMemory = false;
The CompilerParameters
class has a property GenerateInMemory
. We set this to false
because we want to write the assembly to disk and not to memory.
The compilation
After the compilation process we want to know the results, so we create a new instance of the class CompilerResults
. CompilerResults
needs the parameter tempFiles
. These are the files that will be generated by the compiler during compilation. Create an instance of TempFileCollection
and set the path to the location where we create the temporary files. If you want to keep the files after compilation set the second parameter of the TempFileCollection
to true
, otherwise false
:
System.CodeDom.Compiler.TempFileCollection tfc =
new TempFileCollection(Application.StartupPath, false);
CompilerResults cr = new CompilerResults(tfc);
Now we can compile our example code. There are different possibilities that are given by the CodeCompiler. In our case we use the method CompileAssemblyFromSource
:
cr = cc.CompileAssemblyFromSource(cp, this.rtfCode.Text);
System.Collections.Specialized.StringCollection sc = cr.Output;
foreach (string s in sc)
{
Console.WriteLine(s);
}
After compilation we write the created output to the console. In some cases there may be some compiling errors. You can catch them looking at the property Errors
of the CompilerResults
object:
if (cr.Errors.Count > 0)
{
foreach (CompilerError ce in cr.Errors)
{
Console.WriteLine(ce.ErrorNumber + ": " + ce.ErrorText);
}
MessageBox.Show(this, "Errors occoured", "Errors",
MessageBoxButtons.OK, MessageBoxIcon.Error);
this.btnExecute.Enabled = false;
}
else
{
this.btnExecute.Enabled = true;
}
The execution
Now we've compiled our example code and want to execute it. As you can see within the source code of the sample application, I have created an interface called Command
, which includes the method Execute()
. Our example code is the implementation of this interface.
To execute the assembly we have to create a new application domain in which the assembly should be loaded. So that we are able to unload the assembly. We use ShadowCopyFiles
, because of this we are able to override the original assembly:
AppDomainSetup ads = new AppDomainSetup();
ads.ShadowCopyFiles = "true";
AppDomain.CurrentDomain.SetShadowCopyFiles();
AppDomain newDomain = AppDomain.CreateDomain("newDomain");
We've created the new application domain, and now we want to load the assembly by doing the following lines:
byte[] rawAssembly = loadFile("TestClass.dll");
Assembly assembly = newDomain.Load(rawAssembly, null);
OK, the assembly is loaded, now we want to execute the sample code which will show us a simple MessageBox
:
Command testClass =
(Command)assembly.CreateInstance("CSharpScripter.TestClass");
testClass.Execute();
After showing the MessageBox
we will unload the assembly and the application domain:
testClass = null;
assembly = null;
AppDomain.Unload(newDomain);
newDomain = null;
History
- 2005/09/05 - Initial article.
- 2005/10/05 - Added how to catch errors.