Introduction
Class diagrams are important in developing a model for a software system before you actually start programming your software. They provide designers, architects, and developers a graphical interface to manipulate methods, properties, fields, events for data structures like classes, interfaces, structs, and more. In this article, we will use what I call ModelStudio as a designer to help you create your model structures before directly diving into the code, and while we are interacting with this designer, it should be noted that the code will be updated in the background and later auto-generated using the System.CodeDom
namespace.
Background
I think that modeling your code first is a good first step in OOP and designing a great software application architecture. If you are looking for a Reflective solution, then you may want to take a look at something like Sacha Barber's 100% Reflective Class Diagram Creation Tool. For the diagrammer, which I won't discuss in this article, I modified and used the ClassDrawerContainerPanel
and dependent classes from Barber's tool. I also want to acknowledge the use of Chagosoft's cDOMx Class Builder for ideas as well.
Using ModelStudio
The ModelStudio application uses the ModelStudio Framework as we refer to create Visual Basic and C# code documents. To use ModelStudio is fairly straightforward.
- When you start the program, you will be presented with a blank canvas.
- To start creating your first class, simply click the "New Namespace" button, give it a name, and a new
Namespace
object is created. - Then, simply select the namespace from the left to add a "New Import" or "New Class".
- To add properties and methods to a class, simply select the required class and use the "New Property" or "New Method" buttons. To add parameters to methods is very simple as well; simply select the method from the left and click "New Parameter".
- When you are happy with your project, you have these options for getting the code:
- Use the "Generate Code" button to retrieve the code of the project in your desired language.
- Use the "Generate Files" button to generate the individual class documents in the selected folder in the desired language.
- You also have the option to save the project (Repository) as a file for future editing or transport using the "Save" button.
- To open a project file, use the "Open" button, browse to the file, and the project will be loaded.
Purpose
There is one note that I need to add about my purpose for putting this resource tool together. Starting with Visual Studio 2005, Microsoft has included what they call the Class Designer. The Class Designer tool may work for your organization, especially if you have the super duper Visual Studio Team System. However, what I dislike about the Class Designer tool are the 'little things'. For instance, I like for all generated classes to initialize any custom types during class construction.
Books = New List(of Book)
Class Designer neglects to do that. Secondly, wouldn't it be nice to have completed setters and getters for all your properties along with the associated fields auto declared? Class Designer neglects to do this as well. Instead, what Class Designer provides you with is only a prototyped class property by default.
Prototype
Public Overridable Property Name() As String
Get
End Get
Set(ByVal value As String)
End Set
End Property
Ideal
Private m_Name As String
Public Overridable Property Name() As String
Get
Return Me.m_Name
End Get
Set(ByVal value As String)
Me.m_Name = value
End Set
End Property
The Framework
The ModelStudio Framework implements a simple design that first encapsulates the class information into a 'facade' of simple classes. Here we use the term facade in its most liberal sense. Definition: "a superficial appearance or illusion of something. (E.g.: They managed somehow to maintain a facade of wealth)". These facade classes and the data within each is then "translated", which we will discuss later, to create the actual CodeDom classes for code generation.
Facade
CodeDomImport
Description: This is an example facade class that will be used to create a new Imports
(i.e., using
) statement for your generated classes.
Public Class CodeDomImport
Implements ICodeMember
Private m_Import As String = "System.Text"
Private m_Parent As Object
Sub New(ByVal ImportName As String)
m_Import = ImportName
End Sub
Public Property Name() As String Implements ICodeMember.Name
Get
Return m_Import
End Get
Set(ByVal value As String)
m_Import = value
End Set
End Property
Public Property Parent() As Object
Get
Return m_Parent
End Get
Set(ByVal value As Object)
m_Parent= value
End Set
End Property
End Class
CodeGenerator Class
This is the business end of the whole thing. Here is where the actual code gets translated, prepared, and generated to code files.
Basically, the structure of this class is not ideal. However, for the purposes of this article, everything gets done inside this one class. Translation takes place when we return a regular CodeDOM CodeNamespace
.
Translation
Public Function GetNamespace(ByVal _Namespace As CodeDomNamespace) As CodeNamespace
Dim _space As CodeDomNamespace = _Namespace
Dim _ns As CodeNamespace = _Namespace.DOMNameSpace
If _space.Imports.Count > 0 Then
For Each import As CodeDomImport In _space.Imports
Dim im As New CodeNamespaceImport(import.Name)
_ns.Imports.Add(im)
Next
End If
....
End Function
Preparation
Now we need to prepare the code for output the way we want it. During what we call the preparation stage, you will be using the following CodeDom namespaces:
Imports System.CodeDom
Imports System.CodeDom.Compiler
To prepare classes to initialize all custom types during class construction, we use the following code:
Dim mcdClass As CodeTypeDeclaration = _Class.DOMClass
For Each meth As CodeDomMethod In _Class.Constructors
If meth.Parameters.Count > 0 Or _Class.CustomTypes.Count > 0 Then
Dim cnstr As New CodeDom.CodeConstructor
For Each param As CodeDomParameter In meth.Parameters
cnstr.Parameters.Add(New _
CodeParameterDeclarationExpression(param.Type, param.Name))
Next
For Each p As CodeDomProperty In _Class.CustomTypes
Dim this As New CodeThisReferenceExpression()
Dim leftExpression As New CodePropertyReferenceExpression(this, p.Name)
Dim rightExpression As New _
CodeObjectCreateExpression(New CodeTypeReference(p.Type))
Dim assign As New CodeAssignStatement(leftExpression, rightExpression)
cnstr.Statements.Add(assign)
Next p
mcdClass.Members.Add(cnstr)
End If
Next meth
Now, you can change your constructor to initialize any type that you want by simply adjusting the above commands to meet your own individual preferences.
Generation
Here is where we actually output what we've translated and prepared. Obviously, we are going to generate VB code here, but there is also a similar C# generation method as well.
Private Sub GenerateVBFile(ByVal n As CodeNamespace, ByVal OutputDirectory As String)
m_Unit = New CodeCompileUnit
m_Unit.Namespaces.Add(n)
Dim provider As Microsoft.VisualBasic.VBCodeProvider = _
New Microsoft.VisualBasic.VBCodeProvider
For Each c As CodeDom.CodeTypeDeclaration In n.Types
Dim tempUnit As New CodeCompileUnit
tempUnit.Namespaces.Add(New CodeDom.CodeNamespace(n.Name))
tempUnit.Namespaces(0).Types.Add(c)
Dim sourceFile As String = OutputDirectory + _
Path.DirectorySeparatorChar + c.Name + "." + provider.FileExtension
Using tw As New IndentedTextWriter(New StreamWriter(sourceFile, False), " ")
provider.GenerateCodeFromCompileUnit(tempUnit, tw, New CodeGeneratorOptions())
tw.Close()
End Using
Next c
End Sub
Pretty simple, just sets up the private members and calls GenerateCodeFromCompileUnit
from the VBCodeProvider
and spits out a class, which should look like:
Namespace Classroom
Public Class Student
Private m_Name As String
Private m_StudentID As Integer
Private m_MyTeacher As Teacher
Private m_MyChair As Chair
Private m_IsSpecial As Boolean
Private m_Books As List(Of Book)
Private Sub New()
MyBase.New
Me.MyTeacher = New Teacher
Me.MyChair = New Chair
Me.Books = New List(Of Book)
End Sub
Public Overridable Property Name() As [String]
Get
Return Me.m_Name
End Get
Set
Me.m_Name = value
End Set
End Property
...
End Class
End Namespace
Wrap Up
The last part we have to do is import all of the generated classes into our project solution. Build it and run it. Congratulations, now the real work begins.