Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Generate .NET Code in Any Language Using CodeDOM  : Page 3

The CodeDOM namespace contains classes that abstract the idea of code. After defining code in this abstract manner, you can use a language-specific CodeProvider class to generate code in any .NET language from that single abstract CodeDOM representation.

Generate a Simple Class (Steps 6-10)
Now that you have a namespace, a class, and a property, you need to add code statements to make the property do something.

6. Populate each method or property with code by adding statements to it
Member types such as methods and constructors have only one Statements collection, but properties have two: a GetStatements collection and a SetStatements collection. In this class you need to add only one statement to the SimpleClass.Number property—a Get statement that returns the value of the mNumber field. (To create a Return statement, use the CodeMethodReturnStatement class.) Add the new Get statement to the CodeMemberProperty's GetStatements collection.

' create a statement for the Property Get block ' which will be "Return Me.mNumber" p.GetStatements.Add( _ New CodeMethodReturnStatement( _ New CodeFieldReferenceExpression( _ New CodeThisReferenceExpression(), "mNumber")))

That completes the Number property. The VB-generated output for the new property is:

Public Overridable ReadOnly Property _ Number As Int32 Get Return Me.mNumber End Get End Property

Going back to the HasGet and HasSet properties, I was surprised that they were read-write. I expected them to be read-only. In fact, they act a bit strange. Here's how the code generator responds when you set (or don't set) those properties.

You don't have to set the CodeMemberProperty class's HasGet and HasSet properties. If you comment out the two lines that assign values to the Boolean HasGet and HasSet properties (see the code at the end of step 5), you'll get exactly the same output as shown above; in other words, the code generator is smart enough to "know" whether to generate a read-only or read-write property based on the statements in its GetStatements and SetStatements collections. So if you don't set the properties, the emitted code is exactly what you'd expect—creating a property with a Get block but not a Set block causes the code generator to emit a read-only property, while creating both a Get and a Set block causes the generator to emit a read-write property.

You can force the generator to output a Set block even if the SetStatements collection is empty by setting the HasSet property to True. When you do that, the generator emits an empty Set block that looks like this:

Set End Set

The reverse of the preceding statement is not true. If you populate the GetStatements collection, the generator emits a Get block even if you explicitly set the HasGet property to False. Although I didn't test the Set, I expect it acts the same way; populating the collection overrides a HasSet value of False.

Adding a Function or Sub (a void function in C#) method is similar to creating a constructor or a property; you can see why Microsoft abstracted constructors, methods, and properties and other member types into subclasses of a single CodeTypeMembers base class. The following code adds a public method named PrintNumber that prints the value of the read-only Number property using the System.Diagnostics.Debug class's WriteLine method.

' create a public method named "PrintNumber" Dim aMethod As New CodeMemberMethod() aMethod.Name = "PrintNumber" aMethod.Attributes = MemberAttributes.Public ' this will be a Sub method (a void ' function in C#) because the ' ReturnType is Nothing (null) aMethod.ReturnType = _ New CodeTypeReference("Nothing") ' create the statement to print the number aMethod.Statements.Add( _ New CodeMethodInvokeExpression( _ New CodeSnippetExpression( _ "System.Diagnostics.Debug"), _ "WriteLine", _ New CodePropertyReferenceExpression( _ New CodeThisReferenceExpression(), _ "Number")))

The code that creates the single statement for the PrintNumber method is the most interesting part of the preceding code. The CodeDOM contains special classes that represent many types of statements, but when there's no specific class for a statement type (in other words, the statement is not a Return statement, not a variable assignment, not a method invocation, etc.), you use the CodeSnippetExpression class to hold the statement. The code generator emits the contents of CodeSnippetExpression objects literally; in other words, you can output anything you like by putting the code in a CodeSnippetExpression. The VB-generated output for the new property is:

Public Overridable Sub PrintNumber() System.Diagnostics.Debug.WriteLine(Me.Number) End Sub

The property is now complete. You need to add it to the class's Members collection.

7. Add the method or property to the class
After creating the method or property, add it to the class's Members collection.

' add the method to the class aClass.Members.Add(aMethod)

Adding the property completes the code. The CodeDOM code you've written so far contains a complete but abstracted representation of a namespace (the CodeNamespace object) that contains one CodeTypeDeclaration object representing a class. The class has one private Int32 field (mNumber), one public read-only property (Number), and one public method (PrintNumber).

The CodeDOM representation is language-independent; you can use the abstracted form as a template for generating compilable code in any .NET-compliant language. To do that, you must first create an instance of the appropriate code provider for the language you want to generate. Remember that each .NET language has a special code provider class that gives you access to an object that implements the ICodeGenerator interface, which you can use to generate language-specific code. In step 8, I'll show you the code to create both VB.NET and C# versions of the Simple namespace.

8. Create an appropriate code provider object

' To generate VB.NET code ' create a new VBCodeProvider instance Dim VbProvider As New VBCodeProvider() ' To generate C# code Dim CSProvider As New _ Microsoft.CSharp.CSharpCodeProvider()

Note: Code providers are language-specific, so you won't find them in any of the System namespaces. The VBCodeProvider class is in the Microsoft.VisualBasic namespace, and the CSProvider is in the Microsoft.CSharp namespace.

9. Use the code provider's CreateGenerator method to obtain an ICodeGenerator object
Each code provider has a CreateGenerator method. The method returns an object that implements the ICodeGenerator interface. The ICodeGenerator has a number of GenerateFrom... methods that generate code, such as GenerateCodeFromNamespace, GenerateCodeFromExpression, GenerateCodeFromCompileUnit, GenerateCodeFromStatement, and GenerateCodeFromType. Use whichever one meets your needs.

' Generate VB.NET code ' create a new VBCodeProvider instance Dim VbProvider As New VBCodeProvider() ' get a Generator object Dim codeGen As ICodeGenerator = _ VbProvider.CreateGenerator() ' generate the code Dim s As String = getCode(ns, codeGen) ' write the results to a text field Me.txtResult.Text = "VB.NET CODE:" & _ vbCrLf & vbCrLf Me.txtResult.AppendText(s) ' Repeat the sequence to generate C# code ' but with the CSProvider instead. Dim CSProvider As New _ Microsoft.CSharp.CSharpCodeProvider() ' get a Generator object Dim codeGen As ICodeGenerator = _ VbProvider.CreateGenerator() ' generate the code Dim s As String = getCode(ns, codeGen) ' write the results to the Output Window Me.txtResult.AppendText("C# CODE:" & _ vbCrLf & vbCrLf) Me.txtResult.AppendText(s)

The preceding code passes the ICodeGenerator instance to the getCode function, which calls the ICodeGenerator.GenerateCodeFromNamespace method. In the next step, you'll see what this method does.

10. Call one of the generator's GenerateCodeFrom... methods to emit the code.
In this case, because you want to generate a complete namespace, the most appropriate method to use is GenerateCodeFromNamespace. The method accepts:

  • a CodeNamespace parameter containing the abstract representation of the namespace to generate
  • a TextWriter instance into which the generator writes the emitted code
  • a CodeGeneratorOptions instance containing properties that control various options for generating code. One such option is the IndentString property, which specifies how many spaces the generator uses to indent code, used in the getCode method shown below.

Private Function getCode( _ ByVal CodeGenerator As ICodeGenerator) As String ' create a CodeGeneratorOptions instance Dim options As New CodeGeneratorOptions() ' set the indentation level of the ' emitted code options.IndentString = Space(3) ' create a StringWriter Dim sb As New StringBuilder() Dim sw As StringWriter = New StringWriter(sb) ' generate the code CodeGenerator.GenerateCodeFromNamespace( _ ns, sw, options) ' return the result string Return sb.ToString() End Function

Figure 1 shows a form containing the completed code in VB.NET.

Figure 1: When you click the "Create a Simple Class" button, the sample form generates the namespace and class code from the CodeDOM representation and displays it in the TextBox.
Listing 1 shows the output of the completed process in VB.NET, while Listing 2 shows the output in C#.

It's a significant amount of work to write the CodeDOM code to emit even a simple class; however, the potential benefits are enormous. Once written and debugged, you can guarantee that the emitted code will be error-free. You should also recognize that CodeDOM isn't limited to VB.NET and C#; a .NET language developer can implement a code provider and implement ICodeGenerator to emit code in any language. Therefore, the CodeDOM code you write today is very likely to generate code in other languages in the future—even though those languages may not even exist yet!

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date