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.
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.)
Reviewing the Macro Steps for Creating a Code Generator
Now you have an introductory understanding of the CodeDOM Architecture. To review, it is comprised of classes that inherit from CodeObject and each of these classes represents a code construct in .NET. When you are writing a code generator you are orchestrating these CodeObjects in much the same way as you would if you were actually writing the code. The CodeDOM creates what is referred to as a graph. By picking a specific provider the CodeDOM will perform substitution, substituting CodeObjects in the graph for literal code. In general terms, here are the macro steps for writing a code generator:
- Create an instance of the root code graph element, a CodeCompileUnit
- Add a namespace to the root graph element
- Add types to the namespace
- Add members to the types, including methods, properties, events, and fields
- Add lines of code to the methods
- Request a specific provider and a code generator from that provider
- Generate the code from the graph, writing the output to a file stream
- Compile the output source code
In theory writing a code generator is not too difficult. However, actually writing a code generator does require careful attention to detail and a lot of code.
In part 2 of this article I will show you how to write a code generator that generates a VB .NET or C# blank Windows form on the fly. There is quite a bit of code. However, if you have been using .NET you should be familiar with the code in a Windows Form, making it easier to follow along.
There is no way Microsoft could know in advance what all of the types programmers would return from an XML Web Service. Caught between a rock and a hard place, Microsoft wanted to make Web Services easy to consume, but there is no one-size fits all client-side proxy. The solution was found in the CodeDOM. The CodeDOM uses Reflection to discover all of the public properties of types returned from a Web Service and with this knowledge spits out a dynamically generated client-side proxy. The proxy class hides the most difficult aspects of calling a Web Service, making Web Services much easier to consume. The CodeDOM can by used by you to support similar kinds of scenarios.
If you find yourself implementing the same pattern many times, or want to ensure that advanced patterns are coded correctly even by junior programmers then write a code generator to generate the code for them. Writing the code generator (see Part 2 of this article) takes more work then writing the actual code, but the generator can dynamically write different versions of the same pattern based on input parameters. For example, if you write a code generator for a typed collection then you could pass in the type to collect and the code generator will write a flawless typed collection every time, assuming the generator is written correctly.
Check out part 2 of this article—same Bat time same Bat channel—for a relatively complex code generator that will give you plenty of examples to work with.
About the Author
Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for most recent book "Advanced C# Programming" from McGraw-Hill/Osborne on Amazon.com. You can also look for his upcoming book Visual Basic .NET Power Programming from Addison-Wesley. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at firstname.lastname@example.org.
# # #