Occasionally we might come across a problem which lends itself well to the idea of C# code being compiled at runtime. The .NET Framework includes a mechanism called the Code Document Object Model (CodeDOM) that enables developers of programs that emit source code to generate source code in multiple programming languages at run time, based on a single model that represents the code to render. We can use the CSharpCodeProvider to do exactly this, it can compile some code, create an assembly (in my case in-memory) and then allow us to instantiate the code within that assembly.
Let’s assume that we want a simple method to be compiled from a text file at runtime .
mymethod.txt
namespace myTXTnamespace
{
using System;
class Program : ScriptExecutor.Interfaces.IScript
{
public string Run()
{
Console.WriteLine("Script Program Begin");
return "Returned value from Compiled Script";
}
}
}
We need somehow to load this text file and compile it. For that reason the first step is to inform our program how the structure of this class will be. So we create an interface describing the method of the class.
namespace ScriptExecutor.Interfaces
{
public interface IScript
{
string Run();
}
}
After that we have to load and compile the text file.
public static class ScriptHelper
{
public static Assembly CompileAssembly(string[] sourceFiles)
{
var codeProvider = new CSharpCodeProvider();
var compilerParameters = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = true,
IncludeDebugInformation = false,
};
compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
var result = codeProvider.CompileAssemblyFromFile(compilerParameters, sourceFiles);
if (result.Errors.HasErrors) throw new Exception("Assembly compilation failed.");
return result.CompiledAssembly;
}
public static List<Type> GetTypesImplementingInterface(Assembly assembly, Type interfaceType)
{
if (!interfaceType.IsInterface) throw new ArgumentException("Not an interface.", "interfaceType");
return assembly.GetTypes()
.Where(t => interfaceType.IsAssignableFrom(t))
.ToList();
}
}
And finally call the above static method to load the assembly and run the method.
var compiledAssemblyPath = Path.Combine(Environment.CurrentDirectory, ScriptsDirectory);
var sourceFiles = Directory.EnumerateFiles(Environment.CurrentDirectory, "*.txt", SearchOption.AllDirectories).ToArray();
var scriptAssembly = ScriptHelper.CompileAssembly(sourceFiles);
foreach (var scriptType in scriptTypes)
{
var script = (IScript)Activator.CreateInstance(scriptType);
Console.WriteLine( script.Run());
}
For a complete example please check the following repository