Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / Visual Basic
Article

Generating code from code

Rate me:
Please Sign up or sign in to vote.
4.23/5 (13 votes)
14 Jun 20042 min read 99.6K   44   26
This article describes how to programmatically generate and run .NET code.

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:

VB
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.

VB
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:
    VB
    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:

    VB
    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:
    VB
    '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:
    VB
    tw = New StreamWriter(New FileStream(AssemblySourceFile, FileMode.Create))
    GenerateEvalMethod(tw, Expression, cg)
    tw.Close()
    ...
    compRes = cc.CompileAssemblyFromFile(cpar, AssemblySourceFile)

    substituting them with:

    VB
    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


Written By
Technical Lead
Italy Italy
I was born in 1970.

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.

I graduated in Electronic Engineering and earned the following Microsoft certifications:
MCP, MCT, MCDBA, MCSD, MCAD, MCSD for .NET (early achiever).

I worked in IT as a developer, a teacher, a consultant, a technical writer, a technical leader.
IT knowledge applied to real life is my primary interest and focus.

Comments and Discussions

 
QuestionGenerate the Assembly at runtime Pin
NPotti4-Mar-10 2:09
NPotti4-Mar-10 2:09 
AnswerRe: Generate the Assembly at runtime Pin
Alberto Venditti8-Mar-10 0:00
Alberto Venditti8-Mar-10 0:00 
GeneralGreat Job! Pin
Shane Story4-Feb-09 9:33
Shane Story4-Feb-09 9:33 
GeneralRe: Great Job! Pin
Alberto Venditti4-Feb-09 21:27
Alberto Venditti4-Feb-09 21:27 
GeneralVB.Net Dll generator Pin
Member 47743574-Dec-07 12:02
Member 47743574-Dec-07 12:02 
QuestionExecutable Generating App Pin
noodleguy16-Sep-07 7:54
noodleguy16-Sep-07 7:54 
AnswerRe: Executable Generating App Pin
Alberto Venditti16-Sep-07 21:53
Alberto Venditti16-Sep-07 21:53 
AnswerRe: Executable Generating App Pin
Shane Story4-Feb-09 9:30
Shane Story4-Feb-09 9:30 
QuestionA quick question Pin
Arash Sabet14-Apr-07 7:19
Arash Sabet14-Apr-07 7:19 
AnswerRe: A quick question Pin
Alberto Venditti15-Apr-07 11:28
Alberto Venditti15-Apr-07 11:28 
Questioncan a web project use "ReferencedAssemblies.Add("customer.dll")" [modified] Pin
wlbkeats7-Dec-06 23:35
wlbkeats7-Dec-06 23:35 
AnswerRe: can a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
Alberto Venditti8-Dec-06 0:05
Alberto Venditti8-Dec-06 0:05 
GeneralRe: can a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
wlbkeats8-Dec-06 0:15
wlbkeats8-Dec-06 0:15 
GeneralRe: can a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
Alberto Venditti9-Dec-06 8:49
Alberto Venditti9-Dec-06 8:49 
GeneralRe: can a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
wlbkeats10-Dec-06 14:00
wlbkeats10-Dec-06 14:00 
GeneralRe: can a web project use "ReferencedAssemblies.Add("customer.dll")" Pin
Alberto Venditti10-Dec-06 23:00
Alberto Venditti10-Dec-06 23:00 
QuestionWould it possible to add the parameter variebles ? Pin
Kent Liu31-Oct-06 22:19
professionalKent Liu31-Oct-06 22:19 
AnswerRe: Would it possible to add the parameter variebles ? Pin
Alberto Venditti2-Nov-06 21:39
Alberto Venditti2-Nov-06 21:39 
QuestionCan this be done in memory? Pin
Hans.Baumann23-Jun-05 0:32
Hans.Baumann23-Jun-05 0:32 
AnswerRe: Can this be done in memory? Pin
Alberto Venditti11-Jul-05 12:23
Alberto Venditti11-Jul-05 12:23 
GeneralSome remarks Pin
mav.northwind15-Jun-04 20:52
mav.northwind15-Jun-04 20:52 
GeneralRe: Some remarks Pin
Alberto Venditti15-Jun-04 21:38
Alberto Venditti15-Jun-04 21:38 
GeneralRe: Some remarks Pin
mav.northwind15-Jun-04 21:52
mav.northwind15-Jun-04 21:52 
GeneralRe: Some remarks Pin
Alberto Venditti15-Jun-04 23:21
Alberto Venditti15-Jun-04 23:21 
GeneralRe: Some remarks Pin
mav.northwind16-Jun-04 0:33
mav.northwind16-Jun-04 0:33 

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.