Compiling and Running code at runtime

I have wanted to know how to compile and run code at runtime for a long time. I have now got a working solution, so it’s time to share.  I might even learn a better way.

So here is the C# 2.0 code, no extra references are required.

using System;
using System.Collections.Generic;
using System.Text;

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

namespace DynaCode
{
    class Program
    {
        static string[] code = {
            "using System;"+
            "namespace DynaCore"+
            "{"+
            "   public class DynaCore"+
            "   {"+
            "       static public int Main(string str)"+
            "       {"+
            "           return str.Length;"+
            "       }"+
            "   }"+
            "}"};

        static void Main(string[] args)
        {
            CompileAndRun(code);

            Console.ReadKey();
        }

        static void CompileAndRun(string[] code)
        {
            CompilerParameters CompilerParams = new CompilerParameters();
            string outputDirectory = Directory.GetCurrentDirectory();

            CompilerParams.GenerateInMemory = true;
            CompilerParams.TreatWarningsAsErrors = false;
            CompilerParams.GenerateExecutable = false;
            CompilerParams.CompilerOptions = "/optimize";

            string[] references = { "System.dll" };
            CompilerParams.ReferencedAssemblies.AddRange(references);

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);

            if (compile.Errors.HasErrors)
            {
                string text = "Compile error: ";
                foreach (CompilerError ce in compile.Errors)
                {
                    text += "rn" + ce.ToString();
                }
                throw new Exception(text);
            }

            //ExpoloreAssembly(compile.CompiledAssembly);

            Module module = compile.CompiledAssembly.GetModules()[0];
            Type mt = null;
            MethodInfo methInfo = null;

            if (module != null)
            {
                mt = module.GetType("DynaCore.DynaCore");
            }

            if (mt != null)
            {
                methInfo = mt.GetMethod("Main");
            }

            if (methInfo != null)
            {
                Console.WriteLine(methInfo.Invoke(null, new object[] { "here in dyna code" }));
            }
        }

        static void ExpoloreAssembly(Assembly assembly)
        {
            Console.WriteLine("Modules in the assembly:");
            foreach (Module m in assembly.GetModules())
            {
                Console.WriteLine("{0}", m);

                foreach (Type t in m.GetTypes())
                {
                    Console.WriteLine("t{0}", t.Name);

                    foreach (MethodInfo mi in t.GetMethods())
                    {
                        Console.WriteLine("tt{0}", mi.Name);
                    }
                }
            }
        }
    }
}

The foundation of this code was a Dynamically Compiling C# blog post for IronPython.

It took me ages to realise that the string[] that CompileAssemblyFromSource consumes is a single string for each block (file) worth of C# code, not for each line.  New lines and space do not affect the compiler.

I then wasted ages finding my method to call via Invoke.  I have not come up with a good solution yet.  Currently my code is calling a static method, as this is what I need.

The last pain was that Invoke takes an array of objects, but it then maps the array to the method being called, so the method needs to have the signature of the items in the object array. I thought I could just handle object[] as the method input.  This makes sense in hind sight, but at the time it was very painful.

Now that I have working code, and know what to Google for, I have found other posts that go into great detail, like Rick Strahl’s 2002 Dynamically executing code in .Net. I feel like I’ve been under a rock.

Comments:

Christopher Fairbairn 2007-12-05 09:27:50

You can do some pretty powerful things with this API.

Once I worked on a tool that took an XML “config” file and passed it through an XSLT transform in order to generate source to build an assembly that could be loaded by another application. Pass in an XML file and out pops a DLL.

If you haven’t already came across it you should also take a look at the related CodeDOM APIs. With these APIs you can build up an object graph that describes your code (i.e. expression, conditional, if, for, method nodes) and have a specific CodeDOM provider turn that into source code for a given language (VB.NET, C#, MSIL… etc).

This is the technology behind Visual Studio’s form designer etc which enables it to generate VB.NET or C# code behinds.

Doing a quick google search turned up this article which seems to be a pretty nice introduction - http://www.ondotnet.com/pub/a/dotnet/2003/02/03/codedom.html


Simeon 2007-12-05 09:34:57

That’s the exact plan! We have a XML file that describes configuration (that is in a different format), and we plan to use code from XML to help with migration/transformation from older XML schemas to new, if/when we update the schema. But XSLT might make it easier, will keep that in mind.


Simeon Pilgrim 2008-09-04 21:46:36

If DynaCore.Data was from another “file” then defining the string[] code like this works also:

static string[] code = {
    "namespace DynaCore.Data"+
    "{"+
    "   public enum DataEnum"+
    "   {"+
    "       Value = 10"+
    "   }"+
    "}",

    "using System;"+
    "using DynaCore.Data;"+
    "namespace DynaCore"+
    "{"+
    "   public class DynaCore"+
    "   {"+
    "       static public int Main(string[] args)"+
    "       {"+
    "             return (int)DataEnum.Value;"+
    "       }"+
    "   }"+
    "}"};

notice that there are two stings (for each file), therefore you could push all your dynamic code into one batch to build.


Raihan 2008-09-04 20:24:00

Hi,

The following code doesn’t work with this procedure:

"using System;"+
"using DynaCore.Data;"+
"namespace DynaCore.Test"+
"{"+
"   public class DynaCore"+
"   {"+
"       static public int Main(string str)"+
"       {"+
"           return str.Length;"+
"       }"+
"   }"+
"}"};

How do I add references to “DynaCore.Data” as this isn’t an assembly?

Thanks


Simeon Pilgrim 2008-09-04 21:39:00

Where is DynaCore.Data defined? in another dynamic lump of code or as part of the main.exe?

If it’s the main.exe then you can refer to that in the

string[] references = { "System.dll", "DynaCode.exe" };

array, if it’s a separate dynamic lump, then you’ll need to know the generated dll’s name. Which you could find via reflection.

Hmm, now I want to know how todo that.


Kirubakaran 2009-01-26 01:58:47

Hi,

As per Christopher Fairbairn’s statement “Pass in an XML file and out pops a DLL”.
Do you have any valuable source for this?(getting a complete assembly from XML file)

Will be great if you can provide one!


Simeon Pilgrim 2009-01-26 09:15:33

Hello,

Unfortunately, I do not have such code, but it should be not to tricky, I would think…


Winston 2011-11-11 05:11:04

How would i go about setting the target framework? As i would like to use partial methods from framework 3?


Simeon 2011-11-11 13:47:48

Hi Winston,

I would assume that it compiles with the framework that your running process is working in.

I struggle to see why you want to use partial methods, when you have to provide all the code at one time, as it’s a singular assembly that you get back. Thus needs to be self contained.


Winston 2011-11-14 07:06:33

The reason for that is that i would like to create a persistence layer from where other developers could extend the code as needed but the core is generated by the system to reflect the database.

It seems to me that the famework used to compile the code is version 2. The reason for this is that when i haven’t added a missing assembly for example System.Drawing.dll, then i get the following error:

"error CS0012: The type 'System.Drawing.Point' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'."

And when using ILSpy i found the following info from the compiled library.

// C:\MyObject.dll
// MyObject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// Architecture: AnyCPU
// Runtime: .NET 2.0
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyVersion("0.0.0.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]

and

// mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

Simeon 2011-11-14 08:41:07

Sounds like you know more than me, maybe I should be asking you the questions.


Winston 2011-11-15 01:48:34

:) Thanks but i doubt it. But will let you know if i find something.


G Applqust 2012-02-23 08:47:20

Thanks a lot. I found some obscure info on targeting a .NET version so I ended up with this. Also, the refAssemblies can contain “System.Data” for example.

using System.Linq;
// ...your other using, namespace, and class statetements...

public static Assembly Compile(string outputName, string[] code,
                               IEnumerable refAssemblies)
{
    CompilerParameters CompilerParams = new CompilerParameters();
    CompilerParams.OutputAssembly = outputName;
    CompilerParams.GenerateInMemory = true;
    CompilerParams.TreatWarningsAsErrors = false;
    CompilerParams.GenerateExecutable = false;
    CompilerParams.CompilerOptions = "/optimize";

    CompilerParams.ReferencedAssemblies.Add("System.dll");
    CompilerParams.ReferencedAssemblies.Add("System.Core.dll"); // Linq, etc.

    if ( refAssemblies.Any() )
        CompilerParams.ReferencedAssemblies.AddRange(refAssemblies.Select(a => a + ".dll").ToArray());

    SortedDictionary opts = new SortedDictionary();
    opts["CompilerVersion"] = "v3.5"; // .NET target

    using (CSharpCodeProvider provider = new CSharpCodeProvider(opts) )
    {
        CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);

        if (compile.Errors.HasErrors)
        {
            throw new Exception(); // suit to yourself
        }

        return compile.CompiledAssembly;
    }

}

Simeon 2012-02-23 12:43:37

Thanks for finding and posting this information…