How to Generate Large Blocks of Code With Macros in .Net

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@evariste·
0.000 HBD
How to Generate Large Blocks of Code With Macros in .Net
#### What Will I Learn?
Would you like to be able to generate a Class with hundreds of lines of Bug-Free code without buying a third-party tool?  This tutorial will show you how to do it with a Macro. 

#### Requirements
.Net Framework

#### Difficulty
Intermediate

## Generating Error Free Code With Macros

Would you like to be able to generate a Class with hundreds of lines of Bug-Free code without buying a third-party tool?  This tutorial will show you how to do it with a Macro.  When someone mentions Extensibility, Automation, or Add-Ins, some developers have never bothered to investigate the power associated with these terms.  When I showed a developer a Macro recently, he said, "I used to write Macros", and looked as if he had moved past that phase of his .NET experience.  But, actually, Macros are a great tool for generating large blocks of bug-free code.  In this article I am going to show you a macro that will generate an entire complete class to process an input row from a fixed-length field data file.  I often use this type of object in processing fixed field input files.

Remember the Type Statement in VB?  It has now been replaced by the Structure statement in .NET.  But in VB, we often used the Type Statement to define record layouts.  In the Type, as shown below, we also used fixed length strings to define the length of input and output fields.  

#### 1 - VB Type Definition.

```
Type InputRecord
   RecordId As String * 2
   LastName As String * 15
   FirstName As String * 25
   MiddleName As String * 15
   Address1 As String * 35
   Address2 As String * 35
   City As String * 25
   State As String * 25
   Zip As String * 10
   HomePhone As String * 12
   WorkPhone As String * 12
   EmailAddress As String * 50
   LicenseNumber As String * 15
   DLState As string * 2
End Type
```

If you allow the .NET Migration tool to migrate an application from VB, and it contains Type structures as shown above, you will probably not get anything nearly as useful as the Type shown above was in VB.  I am going to use a macro to convert the old VB Type definition into something very useful in .NET.  This will work in .NET or C# if you convert the macro to generate C# code.

First, I will show you the Class created from the Macro and describe its usage.  Then I will show you the Macro itself and how to use it.  Figure 2 shows the Class created by the Macro.  The macro does not actually create a class and add it to the project, but that would only require a few more lines of code in the macro.  Instead, the macro simply adds a Text file to the IDE and creates the code for the class in that window.  I manually added a class to the project and copied the code from the Text Window to overlay the new Class.

#### 2 - Input Class Created by a Macro

```
Public Class InputRecord
   Private _RecordId As String = String.Empty
   Private _LastName As String = String.Empty
   Private _FirstName As String = String.Empty
   Private _MiddleName As String = String.Empty
   Private _Address1 As String = String.Empty
   Private _Address2 As String = String.Empty
   Private _City As String = String.Empty
   Private _State As String = String.Empty
   Private _Zip As String = String.Empty
   Private _HomePhone As String = String.Empty
   Private _WorkPhone As String = String.Empty
   Private _EmailAddress As String = String.Empty
   Private _LicenseNumber As String = String.Empty

   Public Property RecordId() As String
      Get
         Return _RecordId
      End Get
      Set(ByVal Value As String)
         _RecordId = Value
      End Set
   End Property
   Public Property LastName() As String
      Get
         Return _LastName
      End Get
      Set(ByVal Value As String)
         _LastName = Value
      End Set
   End Property
   Public Property FirstName() As String
      Get
         Return _FirstName
      End Get
      Set(ByVal Value As String)
         _FirstName = Value
      End Set
   End Property
   Public Property MiddleName() As String
      Get
         Return _MiddleName
      End Get
      Set(ByVal Value As String)
         _MiddleName = Value
      End Set
   End Property
   Public Property Address1() As String
      Get
         Return _Address1
      End Get
      Set(ByVal Value As String)
         _Address1 = Value
      End Set
   End Property
   Public Property Address2() As String
      Get
         Return _Address2
      End Get
      Set(ByVal Value As String)
         _Address2 = Value
      End Set
   End Property
   Public Property City() As String
      Get
         Return _City
      End Get
      Set(ByVal Value As String)
         _City = Value
      End Set
   End Property
   Public Property State() As String
      Get
         Return _State
      End Get
      Set(ByVal Value As String)
         _State = Value
      End Set
   End Property
   Public Property Zip() As String
      Get
         Return _Zip
      End Get
      Set(ByVal Value As String)
         _Zip = Value
      End Set
   End Property
   Public Property HomePhone() As String
      Get
         Return _HomePhone
      End Get
      Set(ByVal Value As String)
         _HomePhone = Value
      End Set
   End Property
   Public Property WorkPhone() As String
      Get
         Return _WorkPhone
      End Get
      Set(ByVal Value As String)
         _WorkPhone = Value
      End Set
   End Property
   Public Property EmailAddress() As String
      Get
         Return _EmailAddress
      End Get
      Set(ByVal Value As String)
         _EmailAddress = Value
      End Set
   End Property
   Public Property LicenseNumber() As String
      Get
         Return _LicenseNumber
      End Get
      Set(ByVal Value As String)
         _LicenseNumber = Value
      End Set
   End Property

   Private Sub Parse(ByVal line As String)
      RecordId = GetField(line, 1, 2, 2)
      LastName = GetField(line, 3, 17, 15)
      FirstName = GetField(line, 18, 42, 25)
      MiddleName = GetField(line, 43, 57, 15)
      Address1 = GetField(line, 58, 92, 35)
      Address2 = GetField(line, 93, 127, 35)
      City = GetField(line, 128, 152, 25)
      State = GetField(line, 153, 177, 25)
      Zip = GetField(line, 178, 187, 10)
      HomePhone = GetField(line, 188, 199, 12)
      WorkPhone = GetField(line, 200, 211, 12)
      EmailAddress = GetField(line, 212, 261, 50)
      LicenseNumber = GetField(line, 262, 276, 15)
   End Sub
   Public Sub New(ByVal line As String)
      Parse(line)
   End Sub
   Public Function GetField(ByVal dataLine As String, _
      ByVal stChar As Integer, ByVal endChar As Integer, _
      ByVal len As Integer) As String
      If dataLine.Length < 205 Then dataLine += Space(205 - dataLine.Length)
      If stChar >= 1 AndAlso _
         endChar <= 205 AndAlso _
         endChar >= stChar AndAlso _
         dataLine.Length >= endChar AndAlso _
         (endChar - stChar + 1) = Len _
          Then
         Return dataLine.Substring(stChar - 1, endChar - stChar + 1).Trim
      Else
         Throw New Exception("ServiceRequests: GetField() bad input parameters")
      End If
   End Function

End Class
```

The class is made up of two methods, one private variable and associated Public Property for each variable in the original VB Type definition.  The Class acts like an Object Factory when you instantiate an instance of the InputRecord Class.  In other words it returns an instance of itself.  Each of the Properties can then be referenced to retrieve the values of the input line of the input file.  You can see that the constructor of the class parses the input line and fills the private variables.  Typically, you would place the instantiation of the InputRecord objects in a For loop reading through the input file.  In the case of this simple Type definition shown in Figure 1 above, the Macro has created 153 lines of bug-free code.  List 1 below shows how to create an input line object and process the data.

#### 3 - Using the New Class.

```
   Dim inputRec As InputRecord
   Using sr As New System.IO.StreamReader(fileName)
      Dim line As String = sr.ReadLine()
      inputRec = New InputRecord(line)
      With inputRec
         Dim firstName As String = inputRec.FirstName
         Dim lastName As String = inputRec.LastName
         '... use similiar code to retrieve the remainder of the fields

         ' process the input
      End With
   End Using
```

Figure 4 shows the code for the Macro itself.  To use the Macro, put it into a module in the Macro IDE, select the field definitions inside the Type definition, shown in Figure 1, and double-click the Macro name in the Macro Explorer.

#### 4 - Macro for Creating Input Line Objects.

```
    Public Sub CreateInputObjectMethod()
        Dim ts As TextSelection = DTE.ActiveDocument.Selection
        Dim s As String = ts.Text
        Dim mc As MatchCollection = Regex.Matches(s, _
           "^\s*(?<field>\w+)\s+As\s+String\s+\*\s+(?<nbr>\d+)", RegexOptions.Multiline)
        Dim cnt As Integer = 0
        Dim stPtr As Integer = 1
        Dim sb As New Text.StringBuilder(5000)
        Dim sbpriv As New Text.StringBuilder(5000)
        Dim sbprop As New Text.StringBuilder(5000)
        Dim sbClass As New Text.StringBuilder(5000)

        Const gf1 As String = _
          "Public Function GetField(ByVal dataLine As String, " & _
          "ByVal stChar As Integer, ByVal endChar As Integer, " & _
          "ByVal len As Integer) As String"
        Const gf2 As String = _
           "   If dataLine.Length < 205 Then dataLine += Space(205 - dataLine.Length)"
        Const gf3 As String = "   If stChar >= 1 AndAlso _"
        Const gf4 As String = "      endChar <= 205 AndAlso _"
        Const gf5 As String = "      endChar >= stChar AndAlso _"
        Const gf6 As String = "      dataLine.Length >= endChar AndAlso _"
        Const gf7 As String = "      (endChar - stChar + 1) = Len _"
        Const gf8 As String = "       Then"
        Const gf9 As String = _
           "      Return dataLine.Substring(stChar - 1, endChar - stChar + 1).Trim"
        Const gf10 As String = "  Else"
        Const gf11 As String = _
     "     Throw New Exception(""ServiceRequests: GetField() bad input parameters"")"
        Const gf12 As String = "End If"
        Const gf13 As String = "End Function"

        Dim name As String = InputBox("Enter name for new Input Object", "Enter Object Name", "")
        If name.Length > 0 Then
            sbClass.Append("Public Class " & name & vbCrLf)
            sb.Append("   Private Sub Parse(Byval line As String)" & vbCrLf)

            For Each m As Match In mc
                Dim nbr As Integer = CType(m.Groups("nbr").Value, Integer)
                cnt += nbr
                ' create the private var for each match
                sbpriv.Append("   Private _" & m.Groups("field").Value & _
                   " As String = String.Empty" & vbCrLf)
                ' create the matching property
                sbprop.Append("   Public Property " & _
                  m.Groups("field").Value & "() As String" & vbCrLf)
                sbprop.Append("      Get" & vbCrLf)
                sbprop.Append("         Return _" & m.Groups("field").Value & vbCrLf)
                sbprop.Append("      End Get" & vbCrLf)
                sbprop.Append("      Set(ByVal Value As String)" & vbCrLf)
                sbprop.Append("         _" & m.Groups("field").Value & " = Value" & vbCrLf)
                sbprop.Append("      End Set" & vbCrLf)
                sbprop.Append("   End Property" & vbCrLf)

                sb.Append(m.Groups("field").Value & " = GetField(line, " & _
                   stPtr.ToString & ", " & cnt & ", " & _
                   m.Groups("nbr").Value & ")" & vbCrLf)
                stPtr += nbr
            Next

            sb.Append("   End Sub" & vbCrLf)
            sb.Append("   Public Sub New(Byval line As String)" & vbCrLf)
            sb.Append("      Parse(line)" & vbCrLf)
            sb.Append("   End Sub" & vbCrLf)
            sb.Append(gf1 & vbCrLf)
            sb.Append(gf2 & vbCrLf)
            sb.Append(gf3 & vbCrLf)
            sb.Append(gf4 & vbCrLf)
            sb.Append(gf5 & vbCrLf)
            sb.Append(gf6 & vbCrLf)
            sb.Append(gf7 & vbCrLf)
            sb.Append(gf8 & vbCrLf)
            sb.Append(gf9 & vbCrLf)
            sb.Append(gf10 & vbCrLf)
            sb.Append(gf11 & vbCrLf)
            sb.Append(gf12 & vbCrLf)
            sb.Append(gf13 & vbCrLf)

            Debug.WriteLine(sb.ToString)
            DTE.ItemOperations.NewFile("General\Text File")
            sbClass.Append(sbpriv.ToString() & vbCrLf)
            sbClass.Append(sbprop.ToString() & vbCrLf)
            sbClass.Append(sb.ToString() & vbCrLf)
            sbClass.Append("End Class" & vbCrLf)

            DTE.ActiveDocument.Object("TextDocument"). _
              Selection.Insert(sbClass.ToString)
        End If
    End Sub
```

The Macro is not really complex.  You can see that I use a RegularExpression to parse the fields of the Type definition.  I hope this will ignite a new interest in Macros and Extensibility in general.  When I originally wrote this Macro, I was converting a VB application with 12 Type Definitions.  As soon as I completed the Macro, which took less than an hour, I used it to generate 12 Classes with over 1500 lines of bug-free code that would never  have to be debugged at all.

<br /><hr/><em>Posted on <a href="https://utopian.io/utopian-io/@evariste/how-to-generate-large-blocks-of-code-with-macros-in-net">Utopian.io -  Rewarding Open Source Contributors</a></em><hr/>
👍 , , , , , , , ,