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.
- 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
- In the Macros Project Explorer click on the MyMacros project, right-clicking
Add|Add Module from the Project Explorer context menu
- 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.
# # #