Reed Robinson has been talking about the Mobile LOB Accelerator lately. This got me thinking how I could make it easier to write applications based on it. This then lead me to CodeDOM. Ok I am lying but then I can't give away all of my secrets.
So what is CodeDOM? Say you want to generate code. You could write a program or class that can output source by writing to a file. However what happens when you want to manipulate the code before writing for example renaming a class or namespace. This becomes difficult if you are just maintaining a string copy in memory. And worst what happens when now you need to output VB rather than C#. You could write a set of objects that represent parts of the code. That is what CodeDOM is a document model for source code. It is essentially a framework of objects used to generate source code or even compiled code. It can be very quirky but powerful and useful.
So first let me back up and give a simplistic view of compiling an application. From the diagram above you can see that essential a source code file is transformed by the compiler into machine instructions. Ok that sounds familar I have an XML file and it is transformed into HTML via XSLT. So what if rather than a compiler I sent the source code into a parser that returned the structure of source code as a set of objects. I then take these objects manipulate them and then compile them or output them as source code. CodeDOM achieves this by allow you to use a CodeDOMProvider to generate a compiled .NET program or source code in CSharp or VB.NET.
Further investigation into the CodeDOMProvider is that it can generate source from
- CompileUnit - a single grouping of namespaces, think source file.
- Expression - a math or logical expression.
- Member - a property, constructor, method or field in a class.
- Namespace - says it all.
- Statement - a code statement or block of code.
- Type - a type including struct, enum, class or delegate.
The CodeDOM grammer is very complex consisting of 81 objects. For this posting I will just show you have to create a simple Class and output it in VB or C#. The listing is below. The steps are
- Create the Code graph
- Output Source in C#
- Output Source in VB
I put the requirement for VB to show the true power of CodeDOM. The biggest step is the creation of the code graph. The code graph requires a CodeCompileUnit, Namespace, Class, field, property and method. I will leave you to explorer the code below and ponder why I am linking Mobile LOB and CodeDOM.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.CodeDom; using System.CodeDom.Compiler; using Microsoft.CSharp; using Microsoft.VisualBasic; using System.IO; namespace GenerateClass { class Program { public static void GenerateSource(CodeDomProvider provider, CodeCompileUnit codeUnit) { StringBuilder sourceFile; IndentedTextWriter writer; sourceFile = new StringBuilder(@"c:\temp\HelloWorld"); if (provider.FileExtension[0] != '.') { sourceFile.Append('.'); } sourceFile.Append(provider.FileExtension); // Create a TextWriter to a StreamWriter to the output file. writer = new IndentedTextWriter( new StreamWriter(sourceFile.ToString(), false), " "); // Generate source code using the code provider. provider.GenerateCodeFromCompileUnit(codeUnit, writer, new CodeGeneratorOptions()); // Close the output file. writer.Close(); } static void Main(string[] args) { CodeCompileUnit codeUnit; CodeFieldReferenceExpression fieldReference; CodeTypeDeclaration newClass; CodeMemberMethod newMethod; CodeMemberField newField; CodeMemberProperty newProperty; CodeNamespace ns; CodeTypeReference propertyType; CodeDomProvider provider; // Create Unit codeUnit = new CodeCompileUnit(); // Create Namespace ns = new CodeNamespace("CodeDOMTest"); ns.Imports.Add(new CodeNamespaceImport("system.IO")); codeUnit.Namespaces.Add(ns); // Create Class newClass = new CodeTypeDeclaration("HelloClass"); newClass.IsClass = true; newClass.Attributes = MemberAttributes.Public; ns.Types.Add(newClass); // Create Property // Step 1. Create Type to be used by the field and property // Step 2. Create Field // Step 3. Create Property // Step 4. Add Get and Set statements propertyType = new CodeTypeReference(typeof(string)); newField = new CodeMemberField(propertyType,"_name"); newField.Attributes = MemberAttributes.Private; newClass.Members.Add(newField); fieldReference = new CodeFieldReferenceExpression(); fieldReference.FieldName = "_name"; newProperty = new CodeMemberProperty(); newProperty.Name = "Name"; newProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final; newProperty.Type = propertyType; // Get statement will be return _name newProperty.GetStatements.Add(new CodeMethodReturnStatement(fieldReference)); // Set statement will be _name = value newProperty.SetStatements.Add(new CodeAssignStatement(fieldReference, new CodeArgumentReferenceExpression("value"))); newClass.Members.Add(newProperty); // Create Method newMethod = new CodeMemberMethod(); newMethod.Name = "SayHello"; newMethod.Attributes = MemberAttributes.Public | MemberAttributes.Final; newMethod.Statements.Add(new CodeSnippetExpression( "Console.WriteLine(\"Hello \" + Name)")); newClass.Members.Add(newMethod); // Write CSharp GenerateSource(new CSharpCodeProvider(), codeUnit); // Write VB GenerateSource(new VBCodeProvider(), codeUnit); } } }
Comments