Click here to Skip to main content
Click here to Skip to main content

Generating code from code

By , 14 Jun 2004
Rate this:
Please Sign up or sign in to vote.

Introduction

Some days ago, I read the good CodeProject article "Compiling with CodeDom" by Gustavo Bonansea, where he shows how to use .NET classes from the CodeDom namespace, to compile .NET code programmatically (and, of course, without using the command-line!). As rightly mentioned in the final comments of that article, .NET allows you not only to programmatically compile code, but also to programmatically generate code. In the following paragraphs, I will show a simple example of code generation.

Background

Imagine to have to implement an arithmetic expression interpreter, that is a piece of code capable to take an expression as an input string and to compute the result of that expression. The classic approach to this problem is to write a parser that recognizes each token of the input expression (numbers, operators, parenthesis and so on), builds an expression tree representing the expression structure, and then walks that tree to compute the result.

Another approach consists in generating a function that actually computes that expression. If you think about an expression like:

  1 + 2 * 3 - 4 / 5

it would be perfect to compute the result simply invoking a function like:

  Public Shared Function MyMethod() As Single
     Return 1 + 2 * 3 - 4 / 5
  End Function

Well, the code of this sample actually does that, in these steps:

  • it creates the function (as a shared method of the class MyClass in the namespace MyNamespace) through .NET CodeDom;
  • it compiles the generated code in an assembly;
  • it loads the newly-created assembly and dynamically invokes MyMethod.

The Code

Here is the code of a console application that demonstrates the dynamic code generation and execution for the "arithmetic expression interpreter" depicted above.

Imports System
Imports System.Collections
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.IO
Imports System.Reflection
Imports Microsoft.CSharp
Imports Microsoft.VisualBasic


Public Class ParseExpr

  Public Shared Sub Main()
    Console.WriteLine("Expression evaluation demo")
    Console.WriteLine("--------------------------")
    Console.WriteLine("Type an arithmetic expression: ")
    Dim Expression As String = Console.ReadLine()

    Console.WriteLine("Your expression is evaluated as: ")
    Console.WriteLine(ProcessExpression(Expression))
    Console.ReadLine()
  End Sub

  Private Shared Function ProcessExpression(ByVal _
                     Expression As String) As Single
    Dim VBcp As New VBCodeProvider()
    Dim cg As ICodeGenerator = VBcp.CreateGenerator()
    Dim AssemblySourceFile As String = "MyClass.vb"
    Dim AssemblyFile As String = "MyClass.dll"

    ' Generating the assembly source file
    Dim tw As TextWriter
    tw = New StreamWriter(New _
            FileStream(AssemblySourceFile, FileMode.Create))
    GenerateEvalMethod(tw, Expression, cg)
    tw.Close()

    ' Compiling the source file to an assembly DLL (IL code)
    Dim cc As ICodeCompiler = VBcp.CreateCompiler()
    Dim cpar As New CompilerParameters()
    cpar.OutputAssembly = AssemblyFile
    cpar.ReferencedAssemblies.Add("System.dll")
    cpar.GenerateExecutable = False     ' result is a .DLL
    cpar.GenerateInMemory = False
    Dim compRes As CompilerResults
    compRes = cc.CompileAssemblyFromFile(cpar, AssemblySourceFile)

    ' Invoking a method of the newly-created assembly
    Dim ass As [Assembly]
    ass = compRes.CompiledAssembly
    Dim ty As Type = ass.GetType("MyNamespace.MyClass")
    Dim mi As MethodInfo = ty.GetMethod("MyMethod")
    Dim Result As Single = CType(mi.Invoke(Nothing, Nothing), Single)
    Return Result
  End Function


  Private Shared Sub GenerateEvalMethod(ByVal tw As _
      TextWriter, ByVal Expression As String, _
      ByVal CodeGen As ICodeGenerator)

    '> Namespace MyNamespace
    Dim ns As New CodeNamespace("MyNamespace")

    '> Public Class MyClass
    Dim ty As New CodeTypeDeclaration("MyClass")
    ty.IsClass = True
    ty.TypeAttributes = TypeAttributes.Public
    ns.Types.Add(ty)

    Dim mm As New CodeMemberMethod()

    '> ' Expression:...
    mm.Comments.Add(New _
      CodeCommentStatement([String].Format("Expression:{0}", _
      Expression)))

    '> Public Shared Function MyMethod() As Single
    mm.Name = "MyMethod"
    mm.ReturnType = New CodeTypeReference("System.Single")
    mm.Attributes = MemberAttributes.Public Or MemberAttributes.Static

    '> Return ...
    mm.Statements.Add(New _
      CodeMethodReturnStatement(New CodeSnippetExpression(Expression)))
    ty.Members.Add(mm)

    CodeGen.GenerateCodeFromNamespace(ns, tw, Nothing)
  End Sub

End Class

Some notes

  1. Because the input expression is computed as a part of the application code, you can insert in it any valid function. For example:
    System.Math.Pow(5,2) * System.Math.PI

    will correctly compute the area of a circle with radius = 5. This works because the System.dll is already referred with:

    cpar.ReferencedAssemblies.Add("System.dll")

    If you want to support in your expressions other functions as well (also from custom assemblies), just remember to add an item to the ReferencedAssemblies collection.

  2. It has been noted (by mav.northwind - thanks!) that there is no need to actually generate the physical assembly file MyClass.dll, because you can instruct the compiler to generate the assembly in memory. To do this, modify the code so:
    'cpar.OutputAssembly = AssemblyFile    ' remove this line
    cpar.GenerateInMemory = True           ' set this option to True
  3. We can also avoid the physical generation of the MyClass.vb source file, with few modifications to our code. You only need to comment out these lines:
    tw = New StreamWriter(New FileStream(AssemblySourceFile, FileMode.Create))
    GenerateEvalMethod(tw, Expression, cg)
    tw.Close()
    ...
    compRes = cc.CompileAssemblyFromFile(cpar, AssemblySourceFile)

    substituting them with:

    Dim sb As New System.Text.StringBuilder()
    tw = New StringWriter(sb)
    GenerateEvalMethod(tw, Expression, cg)
    ...
    compRes = cc.CompileAssemblyFromSource(cpar, sb.ToString())

Conclusions

Of course, dynamically generating, compiling, and running code in this way is not a good solution when you need maximum performance. But you will agree with me that the flexibility of this approach could be decisive in a lot of situations.

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

About the Author

Alberto Venditti
Technical Lead
Italy Italy
I was born in 1970.
 
I studied Electronic Engineering (graduated in 1997).
 
Subsequently, I passed some Microsoft exams, and currently I'm certified as:
MCP, MCT, MCDBA, MCSD, MCAD, MCSD for .NET (early achiever).
 
My first computer experience dates back to early 80s, with a Sinclair ZX81.
From that time on, as many "friends" say, my IT-illness has increased year by year.

Comments and Discussions

 
GeneralGreat Job! PinmemberShane Story4-Feb-09 9:33 
GeneralRe: Great Job! PinmemberAlberto Venditti4-Feb-09 21:27 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 15 Jun 2004
Article Copyright 2004 by Alberto Venditti
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid