Microsoft & .NET .NET Creating Simplified Code Generators in VS.NET, Part II

Creating Simplified Code Generators in VS.NET, Part II

Code generators are powerful and contribute in diverse circumstances in .NET.

If you have used a Web Service then you benefited from the code generator

relying on the CodeDOM that writes a proxy class, making the Web Service easier

to call. (It is also easy to overlook the existence of the proxy class.) This is

what good technology should do for us; it should solve problems without being

obtrusive.

In the second half of this two part article I will demonstrate how to use the

powerful capabilities of .NET to incorporate simple code generators into VS.NET,

helping you and your co-workers become more productive. The example in this

section actually uses macros and the Automation extensibility model. For more

advanced code generators you have the option of using the Reflection.Emit

namespace or System.CodeDOM. The macro example uses string substitution. The

Reflection.Emit namespace emits CIL (Common Intermediate Language), and the

CodeDOM can generate C#, VB .NET, and J# .NET source code.

Let’s set the stage for the task we are trying to automate and proceed.

Defining the Code Template

Working on a big project recently we had tens of thousands of lines of code.

A significant portion of that code contained properties that modified underlying

fields, as many classes will. I got tired of writing the property statement from

scratch dozens of times per day. To eliminate some of the tedium of writing

property methods, I elected to show VS.NET how to do this work for me. As you

will see the task was not completely automated, but a significant portion of the

typing was handled by VS.NET. (If you are jumping in the middle of this article,

keep in mind that the benefit of writing code generators is to aid productivity

through the speed and accuracy of generated code.)

The first step in using a macro to generate code is to open the Macros IDE,

add a new module, a macro, and stub out the code template. Here are the steps

stubbing a new macro and the syntactical template for a property is shown in

listing 1 and the templatized version is shown in listing 2.

  1. To create a new macro, open Visual Studio .NET—I am using VS.NET 2003,

    but the example works in version 1—and select Tools|Macros|Macros IDE

  2. In the Macros Project Explorer click on the MyMacros project, right-clicking

    Add|Add Module from the Project Explorer context menu

  3. Add a public subroutine named WriteProperty to the module

After step 3 we are ready to stub out the code template. Listing 1 contains a

syntactical example of the property we want to write and listing 2 converts that

example to a template.

Listing 1: The syntax of a field and its associated

property.

Private FField As String

Public Property Field() As String
Get
  Return FField
End Get
Set(ByVal Value As String)
  If (FField = Value) Then Return
  FField = Value
End Set
End Property

Listing 2: A templatized version of a property.

Private Cr As String = Environment.NewLine

Private mask As String = _
  "Public Property {0}() As {1}" + Cr + _
  "  Get" + Cr + _
  "    Return {2}" + Cr + _
  "  End Get" + Cr + _
  "  Set(ByVal Value As {1})" + Cr + _
  "    If ({2} = Value) Then Return" + Cr + _
  "    {2} = Value" + Cr + _
  "  End Set" + Cr + _
  "End Property" + Cr

The code in listing 2 defines a field named mask that contains a

parameterized version of a property. Pagination is used literally to manage

layout and parameters are used to represent aspects of the template that will be

replaced by literal values. The parameter {0} represents the Property name. {1}

represents the Property type, and {2} will be replaced with the underlying field

value.

The next step is to write some code that substitutes literal values for the

parameters in the mask string. We can accomplish this step by using the plain

old vanilla InputBox function. We need to prompt for the property name and data

type and the field name.

Rather than attempting to get all of the code right all at once, we can stage

our solution, testing incremental revisions as we proceed. To that end we can

add the queries to obtain the parameterized values and send the results to the

Output window to ensure that the code is generated correctly. The staged

revisions are shown in listing 3.

Listing 3: Incrementally testing additions to the code

generator.

Imports EnvDTE
Imports System.Diagnostics
Imports System

Public Module Generator

  Private Cr As String = Environment.NewLine

  Private mask As String = _
    "Public Property {0}() As {1}" + Cr + _
    "  Get" + Cr + _
    "    Return {2}" + Cr + _
    "  End Get" + Cr + _
    "  Set(ByVal Value As {1})" + Cr + _
    "    If ({2} = Value) Then Return" + Cr + _
    "    {2} = Value" + Cr + _
    "  End Set" + Cr + _
    "End Property" + Cr


  Public Sub WriteProperty()
    Dim PropertyName As String = _
      InputBox("Property name:", "Property Name")

    Dim PropertyType As String = _
      InputBox("Property type:", "Property Type")

    Dim FieldName As String = _
      InputBox("Field name:", "Field Name")

    Dim Code As String = String.Format(mask, _
      PropertyName, PropertyType, FieldName)

    Debug.WriteLine(Code)
End Sub

Run the macro in the Macros IDE by placing the cursor anywhere in the

WriteProperty subroutine and pressing F5. The code displays three InputBoxes and

then substitutes the parameterized values in mask with the input values, writing

the results to the Output window (shown in figure 1).

Figure 1: The generate code written to the Output window.

The code generates correctly, as depicted in the figure. The next step is to

send the generated code to a VB .NET source code file.

Using the VS.NET Automation Model

VS.NET exposes and extensive automation model that permits one to control

almost every facet of the VS.NET IDE. You can get an idea of how comprehensive

this object model is by exploring the VS.NET help topic

ms-help://MS.VSCC.2003/MS.MSDNQTR.2003APR.1033/vsintro7/html/
vxgrfAutomationObjectModelChart.htm. You can navigate to this section of the

help documentation by plugging the link into the VS.NET address bar.

I have made some assumptions about the state of VS.NET when the macro is run.

The macro assumes that a source code module is open and the cursor is located

where the code will be inserted. In coordination with these pre-conditions,

then, we need to obtain the current source code document from the object model

and insert our code at the current cursor location. The active document can be

obtained from an instance of the design time environment. The class is called

EnvDTE and the Singleton instance is named DTE. The DTE object represents the

running IDE and already exists. All we need to do is interact with the DTE

object. Listing 4 contains the rest of the code we will need to insert our

generated property statement.

Listing 4: Inserting code into the active document.

Imports EnvDTE
Imports System.Diagnostics
Imports System

Public Module Generator

  Private Cr As String = Environment.NewLine

  Private mask As String = _
    "Public Property {0}() As {1}" + Cr + _
    "  Get" + Cr + _
    "    Return {2}" + Cr + _
    "  End Get" + Cr + _
    "  Set(ByVal Value As {1})" + Cr + _
    "    If ({2} = Value) Then Return" + Cr + _
    "    {2} = Value" + Cr + _
    "  End Set" + Cr + _
    "End Property" + Cr


  Public Sub WriteProperty()
    Dim PropertyName As String = _
      InputBox("Property name:", "Property Name")

    Dim PropertyType As String = _
      InputBox("Property type:", "Property Type")

    Dim FieldName As String = _
      InputBox("Field name:", "Field Name")

    Dim Code As String = String.Format(mask, _
      PropertyName, PropertyType, FieldName)

    Debug.WriteLine(Code)

    Dim Selection As TextSelection = DTE.ActiveDocument.Selection

    Selection.Insert( _
      String.Format(mask, PropertyName, PropertyType, FieldName))
  End Sub

The new code is comprised of the last two statements (shown in bold). The

first new statement requests the ActiveDocument and the TextSelection from that

document. This will be the file and location in which the user will want to

insert the code-generated property statement.

At this juncture we can run the completed macro in the Macros IDE (make sure

there is an active source code module in VS.NET), to ensure that all of the

mechanics are in place and working correctly. Once satisfied that the code

generator works correctly we can incorporate the macro for easy access into the

VS.NET IDE.

Integrating the Macro Code Generator into the IDE

The macro can be run from the Command window by typing the complete path to

the macro in the VS.NET Command window. (The path is Macros.[macros

project].[module].[public subroutine]. For our example, the literal path will be

Macros.MyMacros.Generator.WriteProperty.) However, the purpose of writing a code

generator is to improve productivity and opening the Command window, remembering

and typing a long path requires quite a bit of effort. We can do better.

An additional improvement would be to incorporate a button, menu, or hotkey

into VS.NET, associating the shortcut with the macro. The remaining steps

demonstrate how to create a new toolbar, add a button to that toolbar, and

associate our new macro with the toolbutton.

Adding a Toolbar

To add a toolbar to VS.NET select Tools|Customize. Navigate to the Toolbars

tab (see figure 2). Click the new button and type the name of the new toolbar. I

used the name Code for my toolbar.

Figure 2: The Customize dialog showing the Code toolbar after being

added with the New button.

The Code toolbar will be floating after you create it. You can drag and drop

the new floating Code toolbar onto the main toolbar, if you prefer.

Adding a Toolbar Button

With the Customize dialog open we want to add a button to the Toolbar. The

button will invoke our macro. To add the button, click the Commands tab, select

Macros from the Categories list and drag the WriteProperty macro from the

Commands list (figure 3) to the Code toolbar.

Figure 3: Select the Macros category and drag the macro to the toolbar

to create a toolbar button for the macro.

By default the toolbar button will have the same name as the command. Again,

with the Customize dialog open we can right-click on the toolbar button to

further modify values. One such useful modification is to shorten the toolbar

button text. I modified the toolbar button for the macro to just ‘Property’ (see

figure 4). You also have the option of adding an icon and concealing the button

text from the toolbar buttons context menu.

Figure 4: With the Customize dialog open we can customize toolbar

buttons with the associated context menu—as shown in the

figure.

Assigning a Hotkey to the Button

Generally I find shortcuts a helpful aid to productivity. We can further

simplify the process of invoking the macro by associating an available shortcut

with the macro. Using the same Commands tab of the Customize dialog click the

Keyboard button (see figure 3). Find the WriteProperty macro (see figure 5) and

place the cursor in the Press shortcut key(s) edit field. Press the key

combination you’d like to assign to the macro. If you use an existing

combination then you will see—in the Shortcut currently used by edit

field—the command assigned to that shortcut. (I used Ctrl + P, Ctrl + P

to invoke the WriteProperty macro.) When you’ve selected a shortcut click the

Assign button to associate that shortcut with the macro command. Close the

Customize dialog.

Figure 5: Modify the keyboard shortcuts in the Options

dialog.

There are many ways to extend and customize Visual Studio .NET to make you

more productive. Combining macros and keyboard shortcuts is a pretty low-tech

strategy that can yield excellent results.

Before we close I’d like to talk about the code generator for a moment. The

WriteProperty macro does not account for the precise positioning of the

generated code. In its current incarnation pagination has to be handled by the

user. With some modifications one could precisely position and format the

generated code. Formatting and pagination are left to the reader. In addition,

one could shorten the code generator by adopting some simple conventions. For

example, if you adopted an F-prefix for fields then you could modify the macro

to require only the property and type and output the field name—parameter

{2} from listing 4. Further, with a modicum of ingenuity one could write code to

read the fields automatically and generate the property statements. A benefit of

writing modular macros is that you can grow the code base, layering new

functionality over time.

Summary

Visual Studio .NET incorporates macros that are written with the Visual Basic

.NET language. These macros can tap into the tremendous power of the VS.NET

IDE’s automation object model to automate any task desired. By writing modular

macros and growing a shared code base over time a development team can radically

increase their productivity.

Excellent tools promote excellent results. By automating repetitive tasks,

like writing code generators, one can accelerate code production, reduce tedium

and errors, and promote hyper-productivity.

About the Author

Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look

for his upcoming book, Visual Basic .NET Power Coding, from

Addison-Wesley. Paul Kimmel is available to help design and build your .NET

solutions and can be contacted at [email protected].

# # #

Latest Posts

Related Stories