Writing Code Generators with the Code Document Object Model - Part 1
I love my work. I get to travel all over and help other people in lots of companies, design and build software, and in the course of my travels I meet great people like Joe Shook, Robert Golieb, Mark Davis, Lynn Kindler, Sommai Stone, Eric Cotter, Jack Poorte, Rob Howard, Susan Warren, Sondra Scott, and on it goes. Almost every single day I have fun writing software and writing about programming, but I haven't had this much fun since Visual Basic 1.0 or Delphi 1.0 came out. I am talking about writing .NET code. It is a blast. The more I use .NET the more fun I have. My hat goes off to Microsoft.
In this article I want to write about something that you have probably heard but may not be sure about, the Code Document Object Model or CodeDOM.
The CodeDOM is comprised classes in the System.CodeDom and System.CodeDom.Compiler namespaces that let you write code generators that will generate C# and VB .NET code and compile that code. Before you say "well that's what I do with my editor every day" wait a minute. If you find your self consuming a Web Service and saying "wow, that was really easy", well its because a code generator written using the CodeDOM generated a dynamic client-side proxy class for you. That is what made it easy. In fact, the CodeDOM supports ASP.NET, wizards, Web Services, and probably some other things we don't know about.
If you find yourself writing a pattern repeatedly then you can automate this task with the CodeDOM. Now everyone on your team can have a dynamic version of that kind of pattern—say the Factory pattern—letter perfect every single time, once you write the code generator. Let's say you discover the wonder of strongly typed collections—enumerable, bindable to controls, and serializable—you can write a code generator to spit out a specific strongly typed collection every time. Even if you forget how to implement a specific pattern or a typed collection, the code generator won't once you have written it.
There is quite a bit of meticulous work involved in writing code generators with the CodeDOM. However, if you are up to writing .NET code in general then you can write a code generator.
In this part 1 of 2 parts article, I am going to provide you with a conceptual overview of the CodeDOM architecture and review the macro steps for writing a code generator. In part 2 we'll put that information to use and write a moderately complex code generator. After completing parts 1 and 2 of this article—assuming you have a good grasp of .NET and OOP—you should be able to write even complex code generators.
Understanding the CodeDOM Architecture
The System.CodeDOM namespace contains too many classes and too much code to offer a reference guide here. We'll leave the role of reference guide up to the help documentation. Instead, here, what I will do is explain the CodeDOM architecture in way so that you "get" it.
The CodeDOM is comprised of classes that stem from the CodeObject class. A CodeObject is precisely what you would imagine: each child CodeObject class represents some chunk of code that you might write in the editor. When you create a code generator what you are doing is organizing code objects into a hierarchical graph much as you organize code into a hierarchical ordering of text and code constructs. Underneath—if you check rotor—you will see that the CodeObjects perform substitution for the most part.
Thus just as you need a containment unit when writing code, you need one when writing a code generator. You also need namespaces, types, methods, events, attributes, fields, properties, and so on. There are CodeObjects that represent each of these elements.
When you have written you code generator you request a provider. The specific provide, like VBCodeProvider, determines if the code objects are substituted with VB.NET or C# code. The hard part is that you are writing code that generates code. For example, instead of writing an If condition Then statement you are using the CodeConditionStatement CodeObject to represent the If-conditional code. A reasonable person might wonder how Microsoft can know what all of the code objects need to be in advance. The answer relates to the relatively small number of keywords in VB .NET. Keep in mind that a huge variety of code can be written with relatively few VB .NET keywords, and so, the same is true of CodeObjects. A tremendous variety of code generators can be written with comparatively few CodeObjects. And, if you can't find the CodeObject you need then you can use snippets. Snippets are CodeObjects that permit you to enter literal code when you can't find a specific CodeObject. However, keep in mind that CodeObjects can be generated as different languages but literal code snippets cannot be translated automatically. For example, if you can't figure out how to write the code generator for
Not(Component Is Nothing)
then you can write the code to generate this statement as
Dim Snippet As CodeSnippetExpression = _ New CodeSnippetExpression("Not (Component Is Nothing")
However, the snippet is VB .NET code and if you generate the code as C# code then you will get a compiler error because Not(Component Is Nothing) should be written as Component != null in C#. In case you were wondering, the correct way to perform the preceding inequality test is
Dim Expression As CodeBinaryExpression = _ New CodeBinaryOperatorExpression( _ New CodeArgumentReferenceExpression(Argument), _ CodeBinaryOperatorType.IdentityInequality, _ New CodePrimitiveExpression(Nothing))
(Recall that I did say that code generators require meticulous attention to detail. They also require a lot of code.)
The key to writing a successful code generator then is to find and organize the CodeObjects that represent the code that you want to generate, regardless of the language you suspect it will be rendered in. (The CodeDOM is language neutral.)