Introducing Code Generation - Running the Template
(Page 4 of 13 )
Before looking at how you create a CodeDOM graph, you’ll learn how you use it to output code. There are two steps to generation—creating a CodeDOM graph and generating the code. The first step creates a CodeCompileUnit as a container for the CodeDOM graph as well as some other information using BuildGraph. A separate step passes this CodeCompileUnit to the GenerateViaCodeDOM method that generates the code. This method takes the CodeCompileUnit and a CodeDomProvider as parameters. The CodeDomProvider is language specific and indicates how to translate the CodeDOM graph into actual code. Microsoft supplies CodeDOM providers for C#, VB .NET, and J#. To illustrate the flexibility of the CodeDOM, I use a single CodeCompileUnit with both a C# and a VB .NET provider. The result is code output in two languages:
Dim compileUnit As CodeDom.CodeCompileUnit
Dim provider As CodeDom.Compiler.CodeDomProvider
compileUnit = HelloWorldViaCodeDOM.BuildGraph()
provider = New Microsoft.VisualBasic.VBCodeProvider
GenerateViaCodeDOM(IO.Path.Combine(outputDir, _
"HelloWorldViaCodeDOM.vb"), _
provider, compileUnit)
' Use same compile unit to generate C# Hello World
provider = New Microsoft.CSharp.CSharpCodeProvider
GenerateViaCodeDOM(IO.Path.Combine(outputDir, _
"HelloWorldViaCodeDOM.cs"), _
provider, compileUnit)
***********************
NOTE BuildGraph is the template that creates the CodeDOM graph and is shown in the next section “Creating the Template.”
The GenerateViaCodeDOM method contains generic code to process any compile unit. You can call this method multiple times, with different providers to generate code in different languages. The GenerateViaCodeDOM method creates a .NET CodeGenerator from the CodeDOM provider you pass. Like the CodeDOM provider, the CodeDOM generator is language specific:
Private Shared Sub GenerateViaCodeDOM( _
ByVal outputFileName As String, _
ByVal provider As CodeDom.Compiler.CodeDomProvider, _
ByVal compileunit As CodeDom.CodeCompileUnit)
Dim gen As CodeDom.Compiler.ICodeGenerator = _
provider.CreateGenerator()
The GenerateViaCodeDOM method also creates an IndentedTextWriter to contain the output code. You’ll see more about the IndentedTextWriter later in this chapter and in Chapter 3, but it allows easy control of horizontal whitespace in the output. The IndentedTextWriter wraps a stream, and writing to a stream gives you flexibility in how you later use the generated code:
Dim tw As CodeDom.Compiler.IndentedTextWriter
Try
tw = New CodeDom.Compiler.IndentedTextWriter_
(New IO.StreamWriter( _ outputFileName, False), " ")
Once you have everything ready, outputting code is a single call to the GenerateCodeFromCompileUnit method of the code generator. This example uses a new CodeGeneratorOptions object with the default values for generator options:
gen.GenerateCodeFromCompileUnit(compileunit, tw, _
New CodeDom.Compiler.CodeGeneratorOptions)
The remainder of the method handles cleanup in a Finally block that ensures you flush and close the stream before leaving the method:
Finally
If Not tw Is Nothing Then
tw.Flush
tw.Close()
End If
End Try
End Sub
Creating the Template
Generating code in multiple languages is cool, no doubt, but you should consider the cost. Just how hard is it to create the CodeDOM graph for something as simple as the “Hello World” program? The HelloWorldViaCodeDOM.BuildGraph method creates a CodeDOM graph (a CodeCompileUnit) and returns it for further processing:
' Class Summary: Hello World via the CodeDOM
Public Class HelloWorldViaCodeDOM
#Region "Public Methods and Properties" Public Shared Function BuildGraph() As CodeDom.CodeCompileUnit Dim CompileUnit As New CodeDom.CodeCompileUnit
A CodeDOM graph always starts with a collection of namespaces. Each namespace contains Imports or C#’s using statements, comments, and types:
Dim nSpace As New CodeDom.CodeNamespace("HelloWorldViaCodeDOM") CompileUnit.Namespaces.Add(nSpace) nSpace.Imports.Add(New CodeDom.CodeNamespaceImport("System"))
Types can be structures, enums, delegates, classes, and so on. This code creates a type that’s a class named Startup and adds it to the namespace:
Dim clsStartup As New CodeDom.CodeTypeDeclaration("Startup") nSpace.Types.Add(clsStartup)
Types contain members. Members can be fields,2 methods, properties, and so on. You can specify different details depending on the member category, such as an initial value for fields or a collection of statements for methods. CodeEntryPointMethod is a member of the Startup class that specifies a method used to start up the application:
Dim main As New CodeDom.CodeEntryPointMethod
So far, the CodeDOM requires extra work, but the approach is logical and intuitive.
***************
2. Class-level variables are also called fields.
Building individual statements is the difficult and probably nonintuitive part of using the CodeDOM. You separately create each piece of the statement—each variable, literal expression, operator, invocation, and so on. Variables are created for the literal “Hello World,” for the reference to the console class, and for the invocation of the WriteLine method using these two variables. Once you have the statement created, you add it to the statement collection of the method:
Dim exp As New CodeDom.CodePrimitiveExpression("Hello World!") Dim refExp As New CodeDom.CodeTypeReferenceExpression("System.Console") Dim invoke As New CodeDom.CodeMethodInvokeExpression( _
refExp, "WriteLine", exp) main.Statements.Add(New CodeDom.CodeExpressionStatement(invoke))
clsStartup.Members.Add(main)
Return CompileUnit End Function #End Region
End Class
Whew! Some of that didn’t look too bad, but there are four lines of weird code for the one line of output:
Console.WriteLine("Hello World")
And, yes, you have to write code like that for almost every line of .NET code in your target file—and, yes, it gets worse with less simplistic code. That’s the core of why the CodeDOM is hard. By abstracting to this detailed level, it’s hard to read the template to predict the output, and you can’t search in it. Even worse, it isn’t going to produce the same output. Some things require extra work, sometimes with poorly documented features. For example, vertical whitespace, VB .NET option statements, and regions are all difficult or impossible to produce via the CodeDOM.
It’s easy to see why you have to work with this complete abstraction to meet a goal of complete language independence. The generator slams these fragments together quite differently to build code in the different languages. This stuff gets really sticky as the complexity increases. There are 74 classes in the CodeDOM namespace itself as well as many additional classes in the CodeDOM.Compiler name-space. The CodeDOM is workable only for programmers who are tenacious, deal well with abstractions, and have plenty of time.
This chapter is from Code Generation in Microsoft .NET by Kathleen Dollard (Apress, 2004, ISBN: 1590591372). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Plain-Vanilla Code >>
More .NET Articles
More By Apress Publishing