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 pkimmel@softconcepts.com.

# # #

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read