|
1. |
Create a namespace. |
|
2. |
Import namespaces, such as System.Text or System.Drawing. |
|
3. |
Create a class and add it to the namespace. |
|
4. |
Create member fields for the class, defining the name and type for each field. |
|
5. |
Create methods and properties |
|
6. |
Populate each method or property with code by adding statements to it. |
|
7. |
Add the method or property to the class. |
|
8. |
Create an appropriate code provider object. |
|
9. |
Use the code provider's CreateGenerator method to obtain a CodeGenerator object. |
|
10. |
Call one of the generator's GenerateCodeFrom... methods to emit the code. |
Generate a Simple Class (Steps 1-5)
In this section you'll build a simple class that:
' create a namespace
ns = New CodeNamespace("Simple")
A CodeNamespace is a top-level object in the code object model. The CodeNamespace exposes an Imports property that returns a CodeNamespaceImportCollection collection containing the namespaces you want to import. Add each namespace using the collection's Add method ns.Imports.Add(New CodeNamespaceImport _
("System"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Diagnostics"))
ns.Imports.Add(New CodeNamespaceImport _
("System.Text"))
The CodeNamespace object exposes a Types property that returns a CodeTypeDeclarationCollection collection containing the classes (CodeTypeDeclaration objects) in that namespace. When you first create the CodeNamespace, its Types collection is empty. The Types collection holds classes, interfaces, structures, or enumerations, all of which are "types." You add each type to the collection using the Add method. For this example, create one class and add it to the namespace you created in step one. ' create a new class named "SimpleClass"
Dim aClass As New CodeTypeDeclaration _
("SimpleClass")
' add the class to the namespace's
' Types collection
ns.Types.Add(aClass)
4. Create member fields for the class, defining the name and type for each field ' add an Integer field named "mNumber"
Dim aField As New CodeMemberField _
("Int32", "mNumber")
' make it private
aField.Attributes = MemberAttributes.Private
' add it to the class Members collection.
aClass.Members.Add(aField)
5. Create methods and properties ' create a public class constructor
Dim aClassConstructor As New CodeConstructor()
' make it public
aClassConstructor.Attributes = _
MemberAttributes.Public
' the constructor accepts one Integer
' parameter named "aNumber"
aClassConstructor.Parameters.Add( _
New CodeParameterDeclarationExpression( _
New CodeTypeReference("Integer"), "aNumber"))
' assign the value of "aNumber" to the
' mNumber field. The following code
' will output "Me.mNumber = aNumber" in VB.NET
' or "this.mNumber = aNumber" in C#
aClassConstructor.Statements.Add( _
New CodeAssignStatement( _
New CodeFieldReferenceExpression( _
New CodeThisReferenceExpression(), _
"Number"), _
New CodeArgumentReferenceExpression _
("aNumber")))
' add the constructor to the class
aClass.Members.Add(aClassConstructor)
When you generate all the CodeDOM code you've seen thus far using the VBCodeGenerator class, the result is: ' generator output in VB.NET
Imports System
Imports System.Diagnostics
Imports System.Text
Namespace Simple
Public Class SimpleClass
Private mNumber As Int32
Public Sub New(ByVal aNumber As [Integer])
MyBase.New
Me.Number = aNumber
End Sub
Compare the constructor code to the output. Note that the VB code generator understands VB.NET syntax well enough to perform some tasks automatically. For example, creating a constructor generates a Sub New automatically. The End Sub is also automatic. In fact, the code generator creates all End <type> statements (or end brackets if you generate C#) automatically. The CodeThisReferenceExpression in the preceding code is the same as writing Me in VB.NET or this in C#. ' create a public readonly property
' named "Number" that returns an Integer
' create a CodeMemberProperty object
' to represent the property
Dim p As New CodeMemberProperty()
' give the new property a name
p.Name = "Number"
' make it public
p.Attributes = MemberAttributes.Public
p.Type = New CodeTypeReference("Int32")
p.HasGet = True
p.HasSet = False
Pay attention to the HasGet and HasSet propertiesyou'll read more about them in the next section.
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 propertya 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. 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. ' 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. ' 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). ' 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
' 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. 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. |
Simplify the Process
The preceding code shows the linear inline process to create the CodeDOM representation of a class; however, you can simplify the process considerably by wrapping up the code to create various types, members, and statements in easier-to-use methods. For example, here are three wrapper methods that simplify adding member fields to a class, creating a class constructor, and assigning a value to a member field.
Wrapper method to create a member field
' create a member field
Private Function CreateField( _
ByVal fieldType As Type, _
ByVal fieldname As String, _
ByVal scope As MemberAttributes)
Dim cmf As New CodeMemberField(fieldType, _
fieldname)
cmf.Attributes = scope
Return cmf
End Function
Wrapper method to create a constructor ' create a constructor
Private Function CreateConstructor(_
ByVal aClass As CodeTypeDeclaration, _
ByVal scope As MemberAttributes, _
ByVal params As
CodeParameterDeclarationExpressionCollection, _
ByVal Statements As CodeStatementCollection) _
As CodeConstructor
' create a class constructor with the
' appropriate scope attribute
Dim aClassConstructor As New CodeConstructor()
aClassConstructor.Attributes = scope
' assign parameters
If Not params Is Nothing Then
aClassConstructor.Parameters.AddRange(params)
End If
' add statements
If Not Statements Is Nothing Then
aClassConstructor.Statements.AddRange _
(Statements)
End If
Return aClassConstructor
End Function
Wrapper method to assign a vaue to a member field ' assign a value to a member field
Private Function CreateFieldAssignment( _
ByVal aClass As CodeTypeDeclaration, _
ByVal assignTo As String, _
ByVal assignFrom As String)
Return New CodeAssignStatement( _
New CodeFieldReferenceExpression( _
New CodeThisReferenceExpression(), _
assignTo), New
CodeArgumentReferenceExpression _
(assignFrom))
End Function
Even with only these few wrapper methods, you can see that the process to create a new namespace containing a class with a private member field, a constructor with one parameter, and a field assignment is considerably simpler than writing the CodeDOM code directly. Here's an example that uses the wrapper methods rather than the in-line method shown in the first part of this article.
Private Sub btnAutomate_Click(_
ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnAutomate.Click
Dim VbProvider As New VBCodeProvider()
Dim aClass As New CodeTypeDeclaration()
' create a public class
aClass.Name = "AutoGen"
' add a private field
aClass.Members.Add(CreateField( _
GetType(String), "myPrivateVariable", _
MemberAttributes.Private))
' create a parameters collection
Dim params As New _
CodeParameterDeclarationExpressionCollection()
' add a string parameter
params.Add(New _
CodeParameterDeclarationExpression( _
GetType(String), "someValue"))
' create a statements collection
Dim statements As New CodeStatementCollection()
' add a field assignment statement
statements.Add(CreateFieldAssignment(aClass, _
"myPrivateVariable", "someValue"))
' create the class constructor
aClass.Members.Add( _
CreateConstructor( _
aClass, MemberAttributes.Public, _
params, statements))
' generate the code in VB.NET
Dim sb As New StringBuilder()
Dim sw As StringWriter = New StringWriter(sb)
Dim codeGen As ICodeGenerator =
VbProvider.CreateGenerator()
codeGen.GenerateCodeFromType(aClass, sw, Nothing)
' show the results
Me.txtResult.Text = "VB.NET CODE " & _
vbCrLf & vbCrLf
Me.txtResult.AppendText(sb.ToString() & _
vbCrLf & vbCrLf)
' generate the code in C#
sb = New StringBuilder()
sw = New StringWriter(sb)
Dim CSProvider As New _
Microsoft.CSharp.CSharpCodeProvider()
codeGen = CSProvider.CreateGenerator()
codeGen.GenerateCodeFromType(aClass, sw, Nothing)
Me.txtResult.AppendText("C# CODE " & _
vbCrLf & vbCrLf)
Me.txtResult.AppendText(sb.ToString())
End Sub
This time, the code creates a public class named AutoGen with a private String member field named myPrivateVariable. The AutoGen class creates a constructor that accepts a String parameter, and assigns the parameter value to the private member field. When a user clicks the "Automate CodeDOM" button on the sample form to run the preceding method, you'll see this code in the txtResult TextBox (see Figure 2):![]() | |
| Figure 2: When you click the "Automate CodeDOM" button, the sample form generates the code for the AutoGen class and displays it in the TextBox. |
VB.NET CODE
Public Class AutoGen
Private myPrivateVariable As String
Public Sub New(ByVal someValue As String)
MyBase.New
Me.myPrivateVariable = someValue
End Sub
End Class
C# CODE
public class AutoGen {
private string myPrivateVariable;
public AutoGen(string someValue) {
this.myPrivateVariable = someValue;
}
}
Note that this example uses a different GenerateCodeFrom... method. This example contains only a class, not a complete namespace; therefore, the code calls the generator's GenerateCodeFromType method, rather than the GenerateCodeFromNamespace method used in the first example.
| DevX is a division of Internet.com. © Copyright 2010 Internet.com. All Rights Reserved. Legal Notices |