Home Page
Blog

C# EncapsulateField Enhancement

Google

Introduction

Visual Studio has a refactoring snippet that creates a property from a field. This creates the property immediately after the field. This article describes a macro that puts the property further down in the file, with other properties.

The EncapsulateField snippet

The refactoring tools in Visual Studio are great. But I like to keep all my fields in one place and all my properties in another. So they look like:

      #region Fields

      private int variable1;
      private string variable2;
      private double var3;

      #endregion

      #region Properties

      public int Variable1
      {
         get { return variable1; }
         set { variable1 = value; }
      }

      public string Variable2
      {
         get { return variable2; }
         set { variable2 = value; }
      }

      public double Var3
      {
         get { return var3; }
         set { var3 = value; }
      }

      #endregion

Using the EncapsulateField snippet that comes with Visual Studio puts the newly created property directly after the field, so I then have to move it. This is ok once, but gets tedious after you have done it a few hundred times. So I have finally decided to do something about it.

The ExtractProperty Macro

I would have liked to have modified the snippet to move the property for me, but after hunting around for a way to do this, came to the conclusion that it wasn't possible. So I decided to write a macro to do it instead. It uses the built in EncapsulateField snippet to create the property, but then moves it to where other properties are defined.

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Public Module Refactoring

    Dim nCurLine As Int16
    'Dim nCurCol As Int16

    Sub ExtractProperty()

        SaveCursorPosition()

        EncapsulateField()      'Create the property from the field we are on
        DTE.ActiveDocument.Selection.CharLeft() 'Wait for dialogs to be closed
        DTE.ActiveDocument.Selection.CharRight()

        Dim fieldStr As String
        Dim propertyStr As String

        'Get field
        If RegExSelect("<.+[^;]", False) Then
            fieldStr = DTE.ActiveDocument.Selection.Text
            'Get generated property
            If RegExSelect("\n(.|[\n])#\}\n:Wh+\}") Then
                propertyStr = DTE.ActiveDocument.Selection.Text

                Dim fldCount As Int16
                fldCount = Regex.Matches(propertyStr, fieldStr).Count

                'Make sure property matches field (it won't if the user cancelled the refactoring)
                If fldCount = 2 Then

                    DTE.ActiveDocument.Selection.Cut()      'Cut the property out
                    DTE.ActiveDocument.Selection.CharLeft()
                    RegExSelect(";", False)
                    'Get the previous field
                    If RegExSelect("<.+[^;]", False) Then
                        fieldStr = DTE.ActiveDocument.Selection.Text

                        If RegExSelect("get:Wh*\{:Wh*return:Wh+" & fieldStr & ";(.|[\n])#\}\n:Wh*\}") Then
                            DTE.ActiveDocument.Selection.EndOfLine()
                            DTE.ActiveDocument.Selection.Paste()
                        End If

                    End If
                End If
            End If
        End If

        RestoreCursorPosition()

    End Sub

    Private Sub EncapsulateField()

        DTE.ExecuteCommand("Refactor.EncapsulateField")

    End Sub

    Function RegExSelect(ByVal searchExpr As String) As Boolean
        Return RegExSelect(searchExpr, True)
    End Function

    Function RegExSelect(ByVal searchExpr As String, ByVal direction As Boolean) As Boolean

        DTE.Find.FindWhat = searchExpr
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
        DTE.Find.MatchCase = False
        DTE.Find.MatchWholeWord = False
        DTE.Find.Backwards = Not direction
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.Action = vsFindAction.vsFindActionFind

        Return (DTE.Find.Execute() <> vsFindResult.vsFindResultNotFound)

    End Function

    Private Sub SaveCursorPosition()
        nCurLine = DTE.ActiveDocument.Selection.CurrentLine
    End Sub

    Private Sub RestoreCursorPosition()
        DTE.ActiveDocument.Selection.GoToLine(nCurLine)
        DTE.ActiveDocument.Selection.EndOfLine()
    End Sub
End Module

Including the Macro in Visual Studio

To use the macro above, open the macro explorer, create a new module (e.g. "Refactoring"), copy and paste the text above in (replacing the text in the created module) and save it. You should then have a macro called "ExtractProperty". To make it easier to use, you may want to assign a keyboard shortcut to it from the tools, customize menu option.

Using the Macro

The first property that you create will have to be moved manually. This should be done with the built in EncapsulateField snippet. Subsequent fields will be moved to the correct place. For example:

      #region Fields

      private int variable1;
      private string variable2;
      private double var3;

      #endregion

      #region Properties

      public int Variable1
      {
         get { return variable1; }
         set { variable1 = value; }
      }

      #endregion

Putting the cursor anywhere on the line with variable2 and running the macro will produce the following code:

      #region Fields

      private int variable1;
      private string variable2;
      private double var3;

      #endregion

      #region Properties

      public int Variable1
      {
         get { return variable1; }
         set { variable1 = value; }
      }

      public string Variable2
      {
         get { return variable2; }
         set { variable2 = value; }
      }

      #endregion

Note that the macro shouldn't do anything if you cancel the property creation from the EncapsulateField snippet dialog.