Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / XML

A Tool for Dynamic Compile and Run of C# or VB.NET Code

Rate me:
Please Sign up or sign in to vote.
4.27/5 (21 votes)
24 Mar 20042 min read 214K   5.8K   74   49
A tool for dynamic compiling and running of C# or VB.NET code in memory without having to create a project

Introduction

This program compiles a piece of code in memory and runs it dynamically, so you can test some C# or VB.NET code without having to create a new project or solution.

Background

Very often, we need test a piece of code ( C# or VB.NET ). In .NET environment, we use "csc" or "vbc" command; In VS.NET, we create a new project or solution, and create many other files. Both of them are tedious, especially when we test a simple program. One way is produced by jconwell, "Dot Net Script". He uses XML files ( named "dnml" ) which include C# or VB code. In that method, a dnml file is parsed and the code is compiled in memory. That's a good idea! However, it is a little inconvenient because it needs to build a new file. Here, we provide a tool based on his work. The tool has these characters:

  • It can load C# or VB code file, compile the code in memory, and run the code through a static method ( "entry point" );
  • As for VB.NET code, if it is a "Form", the tool can add "Main()" to run the form;
  • It uses a new thread to run the code;
  • It has a visual interface(see below).

Using the Tool

As the picture below shows, press button "Open..." can Open C# or VB.NET file; press button "Paste" to paste code from clipboard to text area; check box "C#" means if the code is C# language; the text box is filled with entry point (a static method in C# or a shared Sub or Function in VB.NET); press button "Run!" to compile and run the code.

Image 1

How It Works

The main class of the program is CompileEngine, which has such a constructor:

C#
public CompileEngine( string code, LanguageType language, string entry )
{
 this.SourceCode = code;
 this.Language = language;
 this.EntryPoint = entry;
}

Run is an important method. It includes three steps: PrepareRealSourceCode, CreateAssembly and CallEntry.

C#
public void Run()
{
 ClearErrMsgs();
 string strRealSourceCode = PrepareRealSourceCode();
 Assembly assembly = CreateAssembly( strRealSourceCode );
 CallEntry( assembly, EntryPoint );
 DisplayErrorMsg();
}

PrepareRealSourceCode has two functions: to add "Imports" statements and to add "Sub Main" for VB.NET Windows Forms.

C#
private string PrepareRealSourceCode()
{
 string strRealSourceCode = "";

 // add some using(Imports) statements
 string strUsingStatement = "";

 if( Language == LanguageType.VB )
 {
  string [] basicImportsStatement = {
     "Imports Microsoft.VisualBasic",
     "Imports System",
     "Imports System.Windows.Forms",
     "Imports System.Drawing",
     "Imports System.Data",
     "Imports System.Threading",
     "Imports System.Xml",
     "Imports System.Collections",
     "Imports System.Diagnostics",
  };
  foreach( string si in basicImportsStatement )
  {
   if( SourceCode.IndexOf( si ) < 0 )
    strUsingStatement += si + "\r\n";
  }
 }

 strRealSourceCode = strUsingStatement + SourceCode;

  // for VB Prog, Add Main(), So We Can Run It
 if( Language == LanguageType.VB && EntryPoint == "Main" &&
  strRealSourceCode.IndexOf( "Sub Main(") < 0 )
 {
  try
  {
   int posClass = strRealSourceCode.IndexOf( "Public Class ")+
      "Public Class ".Length;
   int posClassEnd = strRealSourceCode.IndexOf( "\r\n", posClass );
   string className = strRealSourceCode.Substring( posClass,
        posClassEnd - posClass );
   int pos = strRealSourceCode.LastIndexOf( "End Class");
   if( pos > 0 )
    strRealSourceCode = strRealSourceCode.Substring( 0, pos ) + @"
        Private Shared Sub Main()
         " + "Dim frm As New " + className + "()" + @"
                 If TypeOf frm Is Form Then frm.ShowDialog()
        End Sub
       " + strRealSourceCode.Substring( pos );
  }
  catch{}
 }

 return strRealSourceCode;
}

CreateAssembly compiles the source code and makes assembly in memory.

C#
// compile the source, and create assembly in memory
// this method code is mainly from jconwell,
// see http://www.codeproject.com/dotnet/DotNetScript.asp
private Assembly CreateAssembly(string strRealSourceCode)
{
    //Create an instance whichever code provider that is needed
    CodeDomProvider codeProvider = null;
    if (Language == LanguageType.CSharp )
        codeProvider = new CSharpCodeProvider();
    else if( Language == LanguageType.VB )
        codeProvider = new VBCodeProvider();

    //create the language specific code compiler
    ICodeCompiler compiler = codeProvider.CreateCompiler();

    //add compiler parameters
    CompilerParameters compilerParams = new CompilerParameters();
    compilerParams.CompilerOptions = "/target:library";
           // you can add /optimize
    compilerParams.GenerateExecutable = false;
    compilerParams.GenerateInMemory = true;
    compilerParams.IncludeDebugInformation = false;

    // add some basic references
    compilerParams.ReferencedAssemblies.Add( "mscorlib.dll");
    compilerParams.ReferencedAssemblies.Add( "System.dll");
    compilerParams.ReferencedAssemblies.Add( "System.Data.dll" );
    compilerParams.ReferencedAssemblies.Add( "System.Drawing.dll" );
    compilerParams.ReferencedAssemblies.Add( "System.Xml.dll" );
    compilerParams.ReferencedAssemblies.Add(
          "System.Windows.Forms.dll" );

    if( Language == LanguageType.VB )
    {
        compilerParams.ReferencedAssemblies.Add(
              "Microsoft.VisualBasic.dll" );
    }

    //actually compile the code
    CompilerResults results = compiler.CompileAssemblyFromSource(
            compilerParams,
            strRealSourceCode );

    //get a hold of the actual assembly that was generated
    Assembly generatedAssembly = results.CompiledAssembly;

    //return the assembly
    return generatedAssembly;
}

CallEntry(Assembly...) is used for call entrypoint by reflection.

C#
// invoke the entry method
// this method code is mainly from jconwell,
// see http://www.codeproject.com/dotnet/DotNetScript.asp
    private void CallEntry(Assembly assembly, string entryPoint)
    {
        try
        {
            //Use reflection to call the static Main function
            Module[] mods = assembly.GetModules(false);
            Type[] types = mods[0].GetTypes();

            foreach (Type type in types)
            {
                MethodInfo mi = type.GetMethod(entryPoint,
                        BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                if (mi != null)
                {
                    if( mi.GetParameters().Length == 1 )
                    {
                        if( mi.GetParameters()[0].ParameterType.IsArray )
                        {
                            string [] par = new string[1]; // if Main has string [] arguments
                             mi.Invoke(null, par);
                        }
                    }
                    else
                    {
                        mi.Invoke(null, null);
                    }
                    return;
                }
            }
        }
        catch (Exception ex)
        {
        }
    }

In the FormMain, make a new thread to make a CompileEngine instance and call Run.

C#
private void btnRun_Click(object sender, System.EventArgs e)
{
 // we use a new thread to run it
 new Thread( new ThreadStart( RunTheProg ) ).Start();
}

private void RunTheProg()
{
 string code = txtSource.Text.Trim();
 LanguageType language = chkIsCSharp.Checked ?
    LanguageType.CSharp : LanguageType.VB;
 string entry = txtEntry.Text.Trim();

 if( code == "" ) return;
 if( entry == "" ) entry = "Main";

 CompileEngine engine = new CompileEngine( code, language, entry );

 engine.Run();
}

Some Things That Can Improve

In the PrepareRealSourceCode, it better to use regular expressions to find class name.

Acknowledgement

Thanks jconwell, VictorV, George Orwell, Eric Astor and other friends, who have shown me many excellent works on this topic, such as Dot Net Script, SnippetCompiler, Runtime Compilation.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
China China
He is a professional developer. He use Java, C#, VB, C and many other languages.

Comments and Discussions

 
GeneralMy vote of 5 Pin
DelphiCoder2-Oct-14 21:03
DelphiCoder2-Oct-14 21:03 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey23-Mar-12 1:22
professionalManoj Kumar Choubey23-Mar-12 1:22 
BugWarning! Fundamentally unavoidable memory leak! Don't use this code. Correct solution is known. Pin
Sergey Alexandrovich Kryukov10-Feb-12 15:42
mvaSergey Alexandrovich Kryukov10-Feb-12 15:42 
GeneralRe: Warning! Fundamentally unavoidable memory leak! Don't use this code. Correct solution is known. Pin
DelphiCoder1-Oct-14 19:55
DelphiCoder1-Oct-14 19:55 
With all due respect to SAKryukov,

If you read SAKryukov's many messages within this article and other like this one, you could be easily mislead into believing that AppDomains are the be-all and end-all perfect solution for plugins and that all other avenues are deeply flawed. Not true.

I'm not defending this article, just replying that plug-ins using AppDomains are not the "always safe" and easy solution many falsely represent. Firstly, unless you are running a server where you are constantly swapping code into and out of your App, who cares if the code hangs around until you close your App. Many Apps load plug-ins that remain until the App closes.

Regarding isolation, AppDomains only "help" in this regard. AppDomains still run within the same process as your APP and also use threads supplied by, you guessed it, your own process. Hang a thread in your plug-in and you are normally SOL on unloading that puppy. I've written enough Winsock and System.NET code to know. Hanging a socket within one of your process's threads is an ugly situation not easily remedied. Hang a tread, and that AppDomain is not going to unload until after your entire process has been killed....

In the constantly high volume, high thread count server applications that I've written, I've learned that the only highly reliable way to handle plug-ins is to have them run in an entirely different process (NOT just another AppDomain) and if they hang, to kill that process and start up another. This isn't an easy solution either as now you have to deal with sub applications that serve the Master APP and IPC (Inter-Process Communication), among other things. Your sub applications have to have threads that start and kill other threads using kill-timers if they get hung, and I could go on and on...

None of this is easy unless you are developing a simple plug-in for an app that doesn't need to be unloaded anyway, and then who cares.... The so-called memory leak disappears when your APP is closed anyway, provided Windows is able to close your App's process. Yes I know that unsafe windows resources may still leak, but that isn't a plug-in only related problem. That can happen even within your own application without plug-ins if you don't code properly by releasing Windows resource handles. Sadly a socket is such a handle and it can hang regardless of how you code.

Basically, this is a complicated problem where simple solutions presented in this article and others sharply criticized by SAKryukov and others has having no value, are great for client applications that don't constantly load and unload plug-ins. If that isn't you, then try AppDomains, if you are using many threads that may hang then try running them in another process using IPC, if you need something even more reliable then run a virtual server that you can monitor and automatically fire up another when/if that ever hangs like the big boys do....

The more you dig into the subject, the deeper the hole gets.

Bye...
GeneralOf course, AppDomain-based approach itself does not guarantee right solution Pin
Sergey Alexandrovich Kryukov1-Oct-14 20:13
mvaSergey Alexandrovich Kryukov1-Oct-14 20:13 
GeneralThe Article meets its stated purpose doesn't it? Pin
DelphiCoder2-Oct-14 19:55
DelphiCoder2-Oct-14 19:55 
GeneralHow is that relevant? Pin
Sergey Alexandrovich Kryukov6-Oct-14 7:33
mvaSergey Alexandrovich Kryukov6-Oct-14 7:33 
GeneralRe: How is that relevant? Pin
DelphiCoder6-Oct-14 22:56
DelphiCoder6-Oct-14 22:56 
GeneralRe: How is that relevant? Pin
Sergey Alexandrovich Kryukov7-Oct-14 4:58
mvaSergey Alexandrovich Kryukov7-Oct-14 4:58 
GeneralRe: How is that relevant? Pin
Dominic Burford7-Oct-14 6:24
professionalDominic Burford7-Oct-14 6:24 
GeneralYou need to be correct and rational Pin
Sergey Alexandrovich Kryukov7-Oct-14 6:39
mvaSergey Alexandrovich Kryukov7-Oct-14 6:39 
GeneralRe: You need to be correct and rational Pin
DelphiCoder7-Oct-14 10:15
DelphiCoder7-Oct-14 10:15 
GeneralPlease, keep to the topic Pin
Sergey Alexandrovich Kryukov7-Oct-14 10:24
mvaSergey Alexandrovich Kryukov7-Oct-14 10:24 
GeneralRe: Please, keep to the topic Pin
DelphiCoder7-Oct-14 21:37
DelphiCoder7-Oct-14 21:37 
GeneralRe: You need to be correct and rational Pin
Dominic Burford10-Oct-14 4:22
professionalDominic Burford10-Oct-14 4:22 
GeneralAgree. The only important thing here is warning. Pin
Sergey Alexandrovich Kryukov10-Oct-14 5:23
mvaSergey Alexandrovich Kryukov10-Oct-14 5:23 
GeneralMy vote of 1 Pin
Sergey Alexandrovich Kryukov6-Feb-12 18:50
mvaSergey Alexandrovich Kryukov6-Feb-12 18:50 
GeneralRe: My vote of 1 Pin
mr_neuromante18-Feb-12 3:42
mr_neuromante18-Feb-12 3:42 
GeneralRe: My vote of 1 Pin
Sergey Alexandrovich Kryukov10-Aug-12 6:07
mvaSergey Alexandrovich Kryukov10-Aug-12 6:07 
GeneralMy vote of 5 Pin
Jipat17-Jul-11 22:51
Jipat17-Jul-11 22:51 
Questioncan a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
wlbkeats7-Dec-06 23:31
wlbkeats7-Dec-06 23:31 
NewsDynamic Code Pin
Primadi5-Jun-06 23:58
Primadi5-Jun-06 23:58 
GeneralRe: Dynamic Code Pin
JinweiLee18-Jun-08 22:48
JinweiLee18-Jun-08 22:48 
GeneralRe: Dynamic Code Pin
Sergey Alexandrovich Kryukov6-Feb-12 18:56
mvaSergey Alexandrovich Kryukov6-Feb-12 18:56 
GeneralMemory Leak Pin
mackenb31-Jan-06 5:07
mackenb31-Jan-06 5:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.