Write Macro Code Generators with VS 2005

In his Dr. Dobb’s Journal review of 0972652914, The Nature of Order, the four-volume series by Christopher Alexander, Jacek Sokulski writes “generative processes always operate on the whole, such as in embryo development where the system is not composed from parts, but the whole unfolds from the very beginning. This unfolding is through a sequence of transformations.”

Sokulski mentions that at present software is not automatically generative, but we have been told that software should evolve over generations. So why aren’t we working on automatically generative software? Why aren’t we working on software that grows or evolves automatically over generations of use as opposed to generations of changes? Which technologies do we have at present that support generative software, and which technologies are missing? The answers may not yet exist for those questions, but some capabilities in VS 2005 support generative code.

You can employ macros to write code for you. These macro code generators do not have to be random, and you don’t have to rely on imagination or invention to figure out which kind of macros to write. Two independent but related fields of study—design patterns and refactoring—clearly offer a plethora of options for writing understood, well-documented code generators. (Code generation is not the same thing as generative code, but it is a cog in that engine.) This article demonstrates how to use the macro engine in VS 2005 to write a code generator that implements the refactoring Encapsulate Field for VB.NET.

Refactoring: Encapsulate Field

Refactoring is a defined process for improving the implementation of code. You achieve it by converting random constructs and idioms into a prescribed, predictably reliable use of constructs and idioms. In the simplest sense, refactoring takes some of the subjectivity out of code. As software engineers, we no longer have to depend on argument and force of will to determine whether code is good or not; we can employ an objective standard and agree that refactored code is preferable to code that is not.

Like design patterns, refactorings are named idioms that come replete with descriptions, instructions, and intended results. Any programmer, regardless of experience, can read the description, apply the ordered instructions much like following a recipe, and get a predictable improvement.

An example of a refactoring is called Encapsulate Field. Encapsulate Field is basically the name for making fields private and limiting access to those fields through public property methods. Limited access to an object’s state is preferable to unlimited access, and Encapsulate Field is a belief in the value of constrained access to data. (Some people may not agree with the basic premise—constrained access is preferable to unfettered access—but some people don’t believe objects are good. Rather than debate whether refactoring is good or bad, this article assumes that refactoring is good.)

Implement the Macro

When you are in Visual Studio 2005 in the context of a C# project, a refactoring menu is present. In a VB.NET project, no such menu is currently present (at least by the time beta 2 was released). However, you can easily emulate this supported behavior for VB.NET by writing a lightweight code generator to implement Encapsulate Field (or other refactorings).

To implement Encapsulate Field, automate the following steps:

  • Select a field without a coincidental property method.
  • Change the field’s access modifier to private.
  • Change the field name slightly to avoid a property collision (using any convention you want).
  • Generate the getter and setter property methods and lines of code to provide access to that field.

Tip: A good way to begin working with macros is to turn on the macro recorder, complete a task, and see which macro statements the IDE writes. Next, generalize the recorded macro.

The Visual Studio object model supports all of these capabilities and significantly more. Listing 1 demonstrates an implementation of Encapsulate Field.

Listing 1: Encapsulate Field Written as a Code Generator Using the Macros Capability of VS 2005

Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics

Public Class Refactoring

   Public Shared Sub EncapsulateField()
      Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem
      Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel

      ' Get the current selection
      Dim selection As TextSelection = DTE.ActiveDocument.Selection

      ' Get the current cursor location
      Dim point As TextPoint = selection.ActivePoint

      ' Try to read the current location as a code element
      Dim codeElement As CodeElement = _
          fileCodeModel.CodeElementFromPoint( _
          point, vsCMElement.vsCMElementVariable)

      If (codeElement Is Nothing) Then
         MsgBox("Place mouse cursor on field before running _
            this macro.", _
            MsgBoxStyle.Exclamation)
         Return
      End If

      Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable)

      ' we've tested so we know its a variable
      Dim codeVariable As CodeVariable = CType(codeElement, CodeVariable)
      Dim fieldName As String = codeVariable.Name
      Dim fieldType As String = codeVariable.Type.AsString

      ' rename the field so we don't have a collision with property
      codeVariable.Name = "F" & fieldName

      ' make sure field is private
      codeVariable.Access = vsCMAccess.vsCMAccessPrivate

      ' get the variable's parent
      Dim codeClass As CodeClass = CType(codeVariable.Parent, CodeClass)

      ' add a new property
      Dim codeProperty As CodeProperty = codeClass.AddProperty("dummy", _
          "dummy", fieldType, codeElement)

      codeProperty.Name = fieldName

      ' implement the getter
      Dim getter As EditPoint = codeProperty.Getter.GetStartPoint( _
         vsCMPart.vsCMPartBody).CreateEditPoint
      getter.LineDown()
      getter.Indent(, 3)
      getter.Insert("Return " + codeVariable.Name)
      ' implement the setter
      Dim setter As EditPoint = codeProperty.Setter.GetStartPoint( _
         vsCMPart.vsCMPartBody).CreateEditPoint
      setter.LineDown()
      setter.Indent(, 3)
      setter.Insert(codeVariable.Name + " = Value")
   End Sub
End Class

To try this sample, open the Macros IDE from the Tools|Macros menu in VS 2005, and create a new module under the macro file MyMacros (accessible from the Macros IDE’s Project Explorer).

The object model for the VS IDE is huge, so I won’t try to describe it all here. In addition, the code in Listing 1 is sufficiently commented such that you can readily determine what each chunk of macro code does. Essentially, given the following field:

Public Foo as Integer

The macro adds an F-prefix to the field name (a habit the author picked up from writing tons of Pascal code) and changes the field’s access modifier to Private. Finally, the complete property statement is generated and added to the module containing field:

Private FFoo As Integer
Public Property Foo As Integer
Get
   Return FFoo
End Get
Set(ByVal value As Integer)
   FFoo = value
End Set
End Property

Experiment with the macro code by stepping through it in the Macros IDE.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read