Click here to Skip to main content
15,881,173 members
Articles / Programming Languages / C#
Article

Compiling Source Code from a String

Rate me:
Please Sign up or sign in to vote.
3.97/5 (9 votes)
19 Aug 2009CPOL3 min read 51.5K   296   23   21
A method that uses a CodeDomProvider to compile program code from a string into an Assembly.

Introduction

For an application I'm working on, I need the ability to allow the user to enter some text and have that text be compiled into a class just as if he had typed the class himself and passed it through a compiler. The application will wrap the text in some template code to form a (hopefully valid) program. But then it needs to be compiled so that the application can use the resultant class. The compiled Assembly doesn't need to be stored to disk for future reference, it can get garbage collected once the application is done with it. This method implements that functionality.

Background

Among the various articles here on the Code Project that deal with this topic are:
Dynamic Creation Of Assemblies/Apps[^]
Compiling with CodeDom[^]

Although I had no need to use CodeDom to create the program code, these articles (and MSDN) provided a starting point for my eventual solution.

My Compile method

There really isn't much to the code:

  1. Check the input parameters
  2. Ask the system if the requested language exists
  3. Get a Provider for the requested language
  4. Get default parameters for the Provider's compiler
  5. Set additional parameters as required
  6. Compile the provided code
  7. Check for errors
  8. Return the compiled Assembly

Any Exceptions thrown by called methods will simply be passed along; there's no point catching them here.

public static System.Reflection.Assembly
Compile
(
    string          Code
,
    string          Language
,
    params string[] ReferencedAssemblies
)
{
    if ( System.String.IsNullOrEmpty ( Code ) )
    {
        throw ( new System.ArgumentException
            ( "You must supply some code" , "Code" ) ) ;
    }

    if ( System.String.IsNullOrEmpty ( Language ) )
    {
        throw ( new System.ArgumentException
            ( "You must supply the name of a known language" , "Language" ) ) ;
    }

    if ( !System.CodeDom.Compiler.CodeDomProvider.IsDefinedLanguage ( Language ) )
    {
        throw ( new System.ArgumentException
            ( "That language is not known on this system" , "Language" ) ) ;
    }

    using
    (
        System.CodeDom.Compiler.CodeDomProvider cdp
    =
        System.CodeDom.Compiler.CodeDomProvider.CreateProvider ( Language )
    )
    {
        System.CodeDom.Compiler.CompilerParameters cp =
            System.CodeDom.Compiler.CodeDomProvider.GetCompilerInfo
                ( Language ).CreateDefaultCompilerParameters() ;

        cp.GenerateInMemory = true ;

        cp.TreatWarningsAsErrors = true ;

        cp.WarningLevel = 4 ;

        cp.ReferencedAssemblies.Add ( "System.dll" ) ;

        if
        (
            ( ReferencedAssemblies != null )
        &&
            ( ReferencedAssemblies.Length > 0 )
        )
        {
            cp.ReferencedAssemblies.AddRange ( ReferencedAssemblies ) ;
        }

        System.CodeDom.Compiler.CompilerResults cr =
            cdp.CompileAssemblyFromSource
            (
                cp
            ,
                Code
            ) ;

        if ( cr.Errors.HasErrors )
        {
            System.Exception err = new System.Exception ( "Compilation failure" ) ;

            err.Data [ "Errors" ] = cr.Errors ;

            err.Data [ "Output" ] = cr.Output ;

            throw ( err ) ;
        }

        return ( cr.CompiledAssembly ) ;
    }
}

Using the Code

In the application I'm working on, the user has the option to enter text into a TextBox just as he would write a string literal when writing a program. For instance, in a (C#) program I may write:

string text = "Hello, world!" ;

Ordinarily, in a TextBox, the Hello, World! would not be entered with the quotes, but in a program it's required. Things get even more complex if the text requires escapes:

string text = "Hello, \"Bob\"!" ;
 
or
 
string text = @"Hello, ""Bob""!" ;

Because mistakes can happen when you transcribe data, for this application I want to allow the user (a developer) to be able to copy-and-paste a string literal, exactly as he wrote it in a program, to the TextBox and have it passed through the compiler, just as it will be when his own application gets compiled.

This method:

  1. Accepts the Text from the TextBox
  2. Wraps it in a very simple C# program (notice how)
  3. Uses the CSharp (C#) compiler to compile the code into an Assembly
  4. Retrieves the Type defined by the program
  5. Gets the field that contains the compiled string literal
  6. Retrieves the string literal and returns it
private static string
WrapText
(
    string Text
)
{
    string code = System.String.Format
    (
        @"
        namespace TextWrapper
        {{
            public static class TextWrapper
            {{
                public const string Text = {0} ;
            }}
        }}
        "
    ,
        Text
    ) ;

    System.Reflection.Assembly assm = PIEBALD.Lib.LibSys.Compile
    (
        code
    ,
        "CSharp"
    ) ;

    System.Type type = assm.GetType ( "TextWrapper.TextWrapper" ) ;

    System.Reflection.FieldInfo field = type.GetField
    (
        "Text"
    ,
        System.Reflection.BindingFlags.Public
        |
        System.Reflection.BindingFlags.Static
    ) ;

    return ( (System.String) field.GetValue ( null ) ) ;
}

Note: I intend to support VB.net as well.

CodeDom is in the System.Runtime.Remoting dll, so add a reference.

Conclusion

Overkill? I don't think so. For the samples above, sure, but consider more complex string literals, especially Regular Expressions, which often contain a lot of escapes and may be long enough to warrant being broken onto several lines. For example here's one that's not too big:

private static readonly System.Text.RegularExpressions.Regex HrefReg =
new System.Text.RegularExpressions.Regex
(
    "href\\s*=\\s*(('(?'uri'[^'#]*)?(#(?'id'[^']*))?')" +
    "|(\"(?'uri'[^\"#]*)?(#(?'id'[^\"]*))?\"))"
) ;

To copy-and-paste this the usual way, I'd have to copy the two sections separately, and then remove the escapes. If I edit the string in the TextBox and then want to copy-and-paste it back, I have to reverse the steps, ensuring that I get all the escapes back where they belong.

Using a verbatim string literal would make it easier:

private static readonly System.Text.RegularExpressions.Regex HrefReg =
new System.Text.RegularExpressions.Regex
(
    @"
    href\s*=\s*(('(?'uri'[^'#]*)?(#(?'id'[^']*))?')
    |(""(?'uri'[^""#]*)?(#(?'id'[^""]*))?""))
    "
) ;

Using the technique described in this article, either of these string literals may be copied-and-pasted directly to the TextBox (or wherever) and parsed by the actual compiler that will be used to compile the strings later. If the text is edited away from the source code, it may be copied-and-pasted back without change.

Points of Interest

I wrestled for a long time with trying to load the compiled Assembly into a different AppDomain. I had some success and a lot of headache. Eventually I came across the following comment in the MSDN documentation of AppDomain.RelativeSearchPath[^]:

// Because Load returns an Assembly object, the assemblies must be
// loaded into the current domain as well. This will fail unless the
// current domain also has these directories in its search path.

So that's what was causing the headache. Plus, if loading an Assembly into a different AppDomain doesn't keep the Assembly from being loaded into the current one, then I don't see the point of trying.

History

2009-08-15 First submitted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
BSCS 1992 Wentworth Institute of Technology

Originally from the Boston (MA) area. Lived in SoCal for a while. Now in the Phoenix (AZ) area.

OpenVMS enthusiast, ISO 8601 evangelist, photographer, opinionated SOB, acknowledged pedant and contrarian

---------------

"I would be looking for better tekkies, too. Yours are broken." -- Paul Pedant

"Using fewer technologies is better than using more." -- Rico Mariani

"Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’" -- Steve McConnell

"Every time you write a comment, you should grimace and feel the failure of your ability of expression." -- Unknown

"If you need help knowing what to think, let me know and I'll tell you." -- Jeffrey Snover [MSFT]

"Typing is no substitute for thinking." -- R.W. Hamming

"I find it appalling that you can become a programmer with less training than it takes to become a plumber." -- Bjarne Stroustrup

ZagNut’s Law: Arrogance is inversely proportional to ability.

"Well blow me sideways with a plastic marionette. I've just learned something new - and if I could award you a 100 for that post I would. Way to go you keyboard lovegod you." -- Pete O'Hanlon

"linq'ish" sounds like "inept" in German -- Andreas Gieriet

"Things would be different if I ran the zoo." -- Dr. Seuss

"Wrong is evil, and it must be defeated." –- Jeff Ello

"A good designer must rely on experience, on precise, logical thinking, and on pedantic exactness." -- Nigel Shaw

“It’s always easier to do it the hard way.” -- Blackhart

“If Unix wasn’t so bad that you can’t give it away, Bill Gates would never have succeeded in selling Windows.” -- Blackhart

"Use vertical and horizontal whitespace generously. Generally, all binary operators except '.' and '->' should be separated from their operands by blanks."

"Omit needless local variables." -- Strunk... had he taught programming

Comments and Discussions

 
GeneralMy vote of 1 Pin
Izzet Kerem Kusmezer13-Apr-10 10:30
Izzet Kerem Kusmezer13-Apr-10 10:30 
GeneralRe: My vote of 1 Pin
Izzet Kerem Kusmezer13-Apr-10 10:31
Izzet Kerem Kusmezer13-Apr-10 10:31 
GeneralRe: My vote of 1 Pin
PIEBALDconsult13-Apr-10 11:03
mvePIEBALDconsult13-Apr-10 11:03 
GeneralRe: My vote of 1 Pin
Izzet Kerem Kusmezer14-Apr-10 3:15
Izzet Kerem Kusmezer14-Apr-10 3:15 
GeneralRe: My vote of 1 Pin
PIEBALDconsult14-Apr-10 4:24
mvePIEBALDconsult14-Apr-10 4:24 
GeneralRe: My vote of 1 Pin
Izzet Kerem Kusmezer14-Apr-10 4:27
Izzet Kerem Kusmezer14-Apr-10 4:27 
GeneralRe: My vote of 1 Pin
PIEBALDconsult13-Apr-10 11:04
mvePIEBALDconsult13-Apr-10 11:04 
Other than the ones I mentioned? I didn't find them when I searched. Do you have links?
GeneralInteresting idea. Pin
Oakman24-Aug-09 3:45
Oakman24-Aug-09 3:45 
GeneralRe: Interesting idea. Pin
PIEBALDconsult24-Aug-09 18:03
mvePIEBALDconsult24-Aug-09 18:03 
GeneralRe: Interesting idea. Pin
Oakman25-Aug-09 1:02
Oakman25-Aug-09 1:02 
GeneralRe: Interesting idea. Pin
PIEBALDconsult26-Aug-09 4:17
mvePIEBALDconsult26-Aug-09 4:17 
GeneralMy vote of 1 Pin
Keith Barrow24-Aug-09 3:08
professionalKeith Barrow24-Aug-09 3:08 
GeneralRe: My vote of 1 Pin
PIEBALDconsult24-Aug-09 18:01
mvePIEBALDconsult24-Aug-09 18:01 
GeneralMy vote of 1 Pin
Dmitri Nеstеruk20-Aug-09 5:10
Dmitri Nеstеruk20-Aug-09 5:10 
GeneralRe: My vote of 1 Pin
PIEBALDconsult20-Aug-09 7:56
mvePIEBALDconsult20-Aug-09 7:56 
GeneralIf you are interesed in the subject... Pin
Oleg Shilo19-Aug-09 17:14
Oleg Shilo19-Aug-09 17:14 
GeneralRe: If you are interesed in the subject... Pin
PIEBALDconsult19-Aug-09 18:07
mvePIEBALDconsult19-Aug-09 18:07 
GeneralFormatting PinPopular
N a v a n e e t h19-Aug-09 4:11
N a v a n e e t h19-Aug-09 4:11 
GeneralRe: Formatting Pin
PIEBALDconsult19-Aug-09 4:53
mvePIEBALDconsult19-Aug-09 4:53 
GeneralRe: Formatting Pin
johannesnestler19-Aug-09 5:11
johannesnestler19-Aug-09 5:11 
GeneralRe: Formatting Pin
PIEBALDconsult19-Aug-09 5:27
mvePIEBALDconsult19-Aug-09 5:27 

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.