Thursday, December 18, 2008

Dynamically Compiling Assemblies with CodeDomProvider

A few posts ago, I mentioned the use of CodeDomProvider to compile assemblies at runtime. It is really easy to do.

First, you'll need to include a few namespaces you normally wouldn't:

using System.CodeDom.Compiler;
using Microsoft.CSharp;


Next, setting up the assembly compiler:

CodeDomProvider codeProvider = new CSharpCodeProvider();
CompilerParameters compilerParameters = new CompilerParameters();

compilerParameters.GenerateExecutable = false;
compilerParameters.GenerateInMemory = true;
compilerParameters.IncludeDebugInformation = false;
compilerParameters.WarningLevel = 3;
compilerParameters.TreatWarningsAsErrors = false;
compilerParameters.CompilerOptions = "/optimize";


We're using a CSharpCodeProvider here - there are other code providers for other .NET languages.

Our compiler parameters specify that we are not generating an executable, we want to compile the assembly directly in memory, rather than to disk, and we aren't including debugging information. The warning level determines the threshold for generating compiler warnings. I'm not sure exactly what level 3 does, but it's what I've seen referenced by default. It doesn't really matter, anyway, as we also specify that we aren't treating warnings as errors. Finally, we tell the compiler to optimize.

This next bit is really the only tricky part. We need to tell the compiler what extra assemblies we want to reference. The most general method I've found so far is simply to reference all of the assemblies that our parent program references, as well as the parent assembly itself:

Assembly executingAssembly = Assembly.GetExecutingAssembly();
compilerParameters.ReferencedAssemblies.Add(executingAssembly.Location);

foreach (AssemblyName assemblyName in executingAssembly.GetReferencedAssemblies())
{
compilerParameters.ReferencedAssemblies.Add(Assembly.Load(assemblyName).Location);
}


With the setup done, compiling an assembly is easy:

string assemblyCode = "public class MyClass { ...<Code Goes Here>... }";

CompilerResults compileResults =
codeProvider.CompileAssemblyFromSource(compilerParameters, new string[] { assemblyCode });

Assembly myAssembly = compileResults.CompiledAssembly;


That's all there is to it. Note that you should also check to see if and compile errors were generated. The CompileResults class has an Errors property that holds a collection of the compile errors, if any.

To use your shiny new assembly, simply use reflection to extract your class from the assembly:

Type myType = codeAssembly.GetType("MyClass");


You'll end up with a Type you can use like any other - using Activator.CreateInstance() to create a new instance of your class, for example.

3 comments:

  1. I ran across this post today and it helped me to solve my problem. One note, you need a reference to the System.Reflection namespace which Assembly is a part of, otherwise this will not compile. System.Reflection is appaerntly not added by default in VS2008 WinForms projects.

    ReplyDelete
  2. I love you man :) Saved me a lot of headache!

    ReplyDelete
  3. Really like this. Finally, I solved my problem. Thanks a lot :)

    ReplyDelete