Helixoft Blog

Peter Macej - lead developer of VSdocman - talks about Visual Studio tips and automation

Adding a Strongly Typed Collection to a Project

There's not too much to explain about strongly typed collections. There are many articles describing how to implement them. However, there are not so many add-ins or macros that can generate them automatically for you. I needed these collections in my projects as well so I wrote a macro which asks you for item type, automatically creates class file, adds it to the current project and generates fully documented strongly typed collection. It generates VB code but it can be easily modified to create C# or other language as well.

For example we want a String collection. After invoking the macro we specify the type.

Then we get new file called StringCollection.vb with new StringCollection class:

'''<summary>Represents a collection of String objects.</summary>
'''<remarks>This class is a wrapper around Collection object.</remarks>
Public Class StringCollection
    Implements IEnumerable

    Private _cl As New Collection()

    '''<summary> Creates an empty collection.</summary>
    Public Sub New()
    End Sub

    '''<summary>Adds a member to a StringCollection object.</summary>
    '''<remarks>The Before or After argument must refer to an existing member of the
    '''collection; otherwise, an error occurs.
    '''An error also occurs if a specified Key value matches the key for an existing
    '''member of the collection.
    '''
    '''</remarks>
    '''<param name="Item">An object of String type that specifies the member to add to
    '''the collection. </param>
    '''<param name="Key">  A unique String expression that specifies a key string that
    '''can be used instead of a positional index to access a member of the
    '''collection.</param>
    '''<param name="Before"> An expression that specifies a relative position in the
    '''collection. The member to be added is placed in the collection before the member
    '''identified by the Before argument. If Before is a numeric expression, it must be
    '''a number from 1 to the value of the collection's Count property. If Before is a
    '''String expression, it must correspond to the key string specified when the
    '''member being referred to was added to the collection. You cannot specify both
    '''Before and After.</param>
    '''<param name="After">  An expression that specifies a relative position in the
    '''collection. The member to be added is placed in the collection after the member
    '''identified by the After argument. If After is a numeric expression, it must be a
    '''number from 1 to the value of the collection's Count property. If After is a
    '''String expression, it must correspond to the key string specified when the
    '''member referred to was added to the collection. You cannot specify both Before
    '''and After.</param>
    '''<exception cref="ArgumentException">Both Before and After are specified, or
    '''argument does not refer to an existing member of the collection.</exception>
    '''<exception cref="ArgumentException">The specified Key already
    '''exists.</exception>
    Public Sub Add(ByVal Item As String, Optional ByVal Key As String = Nothing, _
            Optional ByVal Before As Object = Nothing, Optional ByVal After As Object = Nothing)

        _cl.Add(Item, Key, Before, After)
    End Sub

    '''<summary>Returns an integer containing the number of objects in a collection.
    '''Read-only.</summary>
    Public ReadOnly Property Count() As Integer
        Get
            Return _cl.Count
        End Get
    End Property

    '''<summary>Returns a reference to an IEnumerator interface, whose purpose is to
    '''grant access to an enumeration's items.</summary>
    '''<remarks>This method is only implemented to justify IEnumerable interface
    '''implementation.
    '''</remarks>
    Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
        Return _cl.GetEnumerator
    End Function

    '''<summary>Returns a reference to an StringEnumerator interface, whose purpose is to
    '''grant access to an enumeration's items.</summary>
    '''<remarks>When GetEnumerator is called, it constructs an enumerator object
    '''containing the current version number in the collection and a reference to the
    '''collection's items. Every time the enumerator is accessed, it compares the
    '''version of the enumerator with the version of the collection. If the versions do
    '''not match, it means that the collection has changed; an exception then
    '''occurs.</remarks>
    Public Function GetEnumerator() As StringEnumerator
        Return New StringEnumerator(Me)
    End Function

    '''<summary>Returns a specific member of a StringCollection object by position.</summary>
    '''<remarks>If Index doesn't match any existing member of the collection, an error
    '''occurs.
    '''The Item property is the default property for a collection. Therefore, the
    '''following lines of code are equivalent:
    '''$CODE$
    '''Print MyCollection(1)
    '''Print MyCollection.Item(1)
    '''$END-CODE$
    '''</remarks>
    '''<param name="Index">An expression that specifies the position of a member of the
    '''collection. Index must be a number from 1 to the value of the collection's Count
    '''property.</param>
    '''<exception cref="ArgumentException">Index doesn't match an existing member of
    '''the collection.</exception>
    '''<exception cref="IndexOutOfRangeException">Index doesn't match an existing
    '''member of the collection.</exception>
    Default Public Overloads ReadOnly Property Item(ByVal Index As Integer) As String
        Get
            Return CType(_cl.Item(Index), String)
        End Get
    End Property

    '''<summary>Returns a specific member of a StringCollection object by key.</summary>
    '''<remarks>If Index doesn't match any existing member of the collection, an error
    '''occurs.
    '''The Item property is the default property for a collection. Therefore, the
    '''following lines of code are equivalent:
    '''$CODE$
    '''Print MyCollection("myKey")
    '''Print MyCollection.Item("myKey")
    '''$END-CODE$
    '''</remarks>
    '''<param name="Index">An expression that specifies the position of a member of the
    '''collection. it must correspond to the key value specified when the member
    '''referred to was added to the collection. </param>
    '''<exception cref="ArgumentException">Index doesn't match an existing member of
    '''the collection.</exception>
    '''<exception cref="IndexOutOfRangeException">Index doesn't match an existing
    '''member of the collection.</exception>
    Default Public Overloads ReadOnly Property Item(ByVal Index As String) As String
        Get
            Return CType(_cl.Item(Index), String)
        End Get
    End Property

    '''<summary>Removes a member from a StringCollection object.</summary>
    '''<param name="Key">A unique String expression that specifies a key string that
    '''can be used, instead of a positional index, to access a member of the
    '''collection. It must correspond to the Key argument specified when the member
    '''referred to was added to the collection.</param>
    '''<exception cref="ArgumentException">Key is invalid or not specified.</exception>
    Public Overloads Sub Remove(ByVal Key As String)
        _cl.Remove(Key)
    End Sub

    '''<summary>Removes a member from a StringCollection object.</summary>
    '''<param name="Index">An expression that specifies the position of a member of the
    '''collection. Index must be a number from 1 to the value of the collection's Count
    '''property.</param>
    '''<exception cref="IndexOutOfRangeException">Index does not match an existing
    '''member of the collection.</exception>
    Public Overloads Sub Remove(ByVal Index As Integer)
        _cl.Remove(Index)
    End Sub

    Public Overrides Function ToString() As String
        Return _cl.ToString()
    End Function

#Region "Custom enumerator"
    '''<summary>Enumerator class for String objects.</summary>
    '''<remarks>Inner class for strongly typed For Each operations.</remarks>
    Public Class StringEnumerator
        Implements IEnumerator

        Private position As Integer = 0
        Private col As StringCollection

        Public Sub New(ByVal Strings As StringCollection)
            Me.col = Strings
        End Sub

        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            If position <col.Count Then
                position += 1
                Return True
            Else
                Return False
            End If
        End Function

        Public Sub Reset() Implements IEnumerator.Reset
            position = 0
        End Sub

        '''<summary>Returns current item.</summary>
        '''<remarks>This method is only implemented to justify IEnumerator interface
        '''implementation.
        '''</remarks>
        Private ReadOnly Property IEnumerator_Current() As Object Implements IEnumerator.Current
            Get
                Return Current
            End Get
        End Property

        '''<summary>Returns current item.</summary>
        Public ReadOnly Property Current() As String
            Get
                Return col.Item(position)
            End Get
        End Property
    End Class
#End Region
End Class

As you can see, the class is fully documented and you can use for example our VSdocman to generate help, IntelliSense and F1 help for it. The class is just wrapper on classic Collection class. There are also other approaches recommended but most of them don't offer access to the item by numerical and string key at the same time, exactly as Collection class. And I really needed this functionality.

You can modify the macro to generate the code you want, even for other language.

Here is the macro:

    '''<summary> This macro adds new collection class file to the current
    '''project.</summary>
    '''<remarks>The class contains the full code for strongly typed collection  with
    '''user-defined type. User is asked for type name. The new class  has name
    '''Type_NameCollection.</remarks>
    Sub addStronglyTypedCollection()
        Dim type As String = InputBox("Enter the type (class) of collection items", "Collection Type")
        If type.Length = 0 Then
            Exit Sub
        End If
        DTE.ItemOperations.AddNewItem("Local Project Items\Class", type + "Collection.vb")

        Try
            DTE.UndoContext.Open("InsertCustomCollectionCode")
            Dim txt As TextSelection
            txt = DTE.ActiveDocument.Selection
            txt.SelectAll()
            txt.Delete()
            txt.Insert(String.Format(collTemplate, type))
            DTE.UndoContext.Close()
        Catch ex As System.Exception
        End Try
    End Sub

#Region "           Collection class template"
    Private collTemplate As String = "'''<summary>Represents a collection of {0} objects.</summary>" + vbCrLf + "'''<remarks>This c" + _
        "lass is a wrapper around Collection object.</remarks>" + vbCrLf + "Public Class {0}Collection" + _
        "" + vbCrLf + "    Implements IEnumerable" + vbCrLf + "" + vbCrLf + "    Private _cl As New Collection()" + vbCrLf + "" + vbCrLf + "    '''<summar" + _
        "y> Creates an empty collection.</summary>" + vbCrLf + "    Public Sub New()" + vbCrLf + "    End Sub" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "   " + _
        " '''<summary>Adds a member to a {0}Collection object.</summary>" + vbCrLf + "    '''<remarks>" + _
        "The Before or After argument must refer to an existing member of the" + vbCrLf + "    '''coll" + _
        "ection; otherwise, an error occurs." + vbCrLf + "    '''An error also occurs if a specified K" + _
        "ey value matches the key for an existing" + vbCrLf + "    '''member of the collection." + vbCrLf + "    ''" + _
        "'" + vbCrLf + "    '''</remarks>" + vbCrLf + "    '''<param name=""Item"">An object of {0} type that speci" + _
        "fies the member to add to" + vbCrLf + "    '''the collection. </param>" + vbCrLf + "    '''<param name=""K" + _
        "ey"">  A unique String expression that specifies a key string that" + vbCrLf + "    '''can be" + _
        " used instead of a positional index to access a member of the" + vbCrLf + "    '''collection." + _
        "</param>" + vbCrLf + "    '''<param name=""Before""> An expression that specifies a relative " + _
        "position in the" + vbCrLf + "    '''collection. The member to be added is placed in the colle" + _
        "ction before the member" + vbCrLf + "    '''identified by the Before argument. If Before is a" + _
        " numeric expression, it must be" + vbCrLf + "    '''a number from 1 to the value of the colle" + _
        "ction's Count property. If Before is a" + vbCrLf + "    '''String expression, it must corresp" + _
        "ond to the key string specified when the" + vbCrLf + "    '''member being referred to was add" + _
        "ed to the collection. You cannot specify both" + vbCrLf + "    '''Before and After.</param>" + vbCrLf + " " + _
        "   '''<param name=""After"">  An expression that specifies a relative position i" + _
        "n the" + vbCrLf + "    '''collection. The member to be added is placed in the collection afte" + _
        "r the member" + vbCrLf + "    '''identified by the After argument. If After is a numeric expr" + _
        "ession, it must be a" + vbCrLf + "    '''number from 1 to the value of the collection's Count" + _
        " property. If After is a" + vbCrLf + "    '''String expression, it must correspond to the key" + _
        " string specified when the" + vbCrLf + "    '''member referred to was added to the collection" + _
        ". You cannot specify both Before" + vbCrLf + "    '''and After.</param>" + vbCrLf + "    '''<exception cre" + _
        "f=""ArgumentException"">Both Before and After are specified, or" + vbCrLf + "    '''argument " + _
        "does not refer to an existing member of the collection.</exception>" + vbCrLf + "    '''<exce" + _
        "ption cref=""ArgumentException"">The specified Key already" + vbCrLf + "    '''exists.</excep" + _
        "tion>" + vbCrLf + "    Public Sub Add(ByVal Item As {0}, Optional ByVal Key As String = Nothi" + _
        "ng, _" + vbCrLf + "            Optional ByVal Before As Object = Nothing, Optional ByVal Afte" + _
        "r As Object = Nothing)" + vbCrLf + "" + vbCrLf + "        _cl.Add(Item, Key, Before, After)" + vbCrLf + "    End Sub" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "" + _
        "    '''<summary>Returns an integer containing the number of objects in a collect" + _
        "ion." + vbCrLf + "    '''Read-only.</summary>" + vbCrLf + "    Public ReadOnly Property Count() As Integer" + _
        "" + vbCrLf + "        Get" + vbCrLf + "            Return _cl.Count" + vbCrLf + "        End Get" + vbCrLf + "    End Property" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "   " + _
        " '''<summary>Returns a reference to an IEnumerator interface, whose purpose is t" + _
        "o" + vbCrLf + "    '''grant access to an enumeration's items.</summary>" + vbCrLf + "    '''<remarks>This " + _
        "method is only implemented to justify IEnumerable interface" + vbCrLf + "    '''implementatio" + _
        "n." + vbCrLf + "    '''</remarks>" + vbCrLf + "    Private Function IEnumerable_GetEnumerator() As IEnumer" + _
        "ator Implements IEnumerable.GetEnumerator" + vbCrLf + "        Return _cl.GetEnumerator" + vbCrLf + "    E" + _
        "nd Function" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "    '''<summary>Returns a reference to an {0}Enumerator interface," + _
        " whose purpose is to" + vbCrLf + "    '''grant access to an enumeration's items.</summary>" + vbCrLf + "  " + _
        "  '''<remarks>When GetEnumerator is called, it constructs an enumerator object" + vbCrLf + " " + _
        "   '''containing the current version number in the collection and a reference to" + _
        " the" + vbCrLf + "    '''collection's items. Every time the enumerator is accessed, it compar" + _
        "es the" + vbCrLf + "    '''version of the enumerator with the version of the collection. If t" + _
        "he versions do" + vbCrLf + "    '''not match, it means that the collection has changed; an ex" + _
        "ception then" + vbCrLf + "    '''occurs.</remarks>" + vbCrLf + "    Public Function GetEnumerator() As {0}" + _
        "Enumerator" + vbCrLf + "        Return New {0}Enumerator(Me)" + vbCrLf + "    End Function" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "    '''<summa" + _
        "ry>Returns a specific member of a {0}Collection object by position.</summary>" + vbCrLf + "  " + _
        "  '''<remarks>If Index doesn't match any existing member of the collection, an e" + _
        "rror" + vbCrLf + "    '''occurs." + vbCrLf + "    '''The Item property is the default property for a colle" + _
        "ction. Therefore, the" + vbCrLf + "    '''following lines of code are equivalent:" + vbCrLf + "    '''$COD" + _
        "E$" + vbCrLf + "    '''Print MyCollection(1)" + vbCrLf + "    '''Print MyCollection.Item(1)" + vbCrLf + "    '''$END-CO" + _
        "DE$" + vbCrLf + "    '''</remarks>" + vbCrLf + "    '''<param name=""Index"">An expression that specifies " + _
        "the position of a member of the" + vbCrLf + "    '''collection. Index must be a number from 1" + _
        " to the value of the collection's Count" + vbCrLf + "    '''property.</param>" + vbCrLf + "    '''<excepti" + _
        "on cref=""ArgumentException"">Index doesn't match an existing member of" + vbCrLf + "    '''t" + _
        "he collection.</exception>" + vbCrLf + "    '''<exception cref=""IndexOutOfRangeException"">I" + _
        "ndex doesn't match an existing" + vbCrLf + "    '''member of the collection.</exception>" + vbCrLf + "    " + _
        "Default Public Overloads ReadOnly Property Item(ByVal Index As Integer) As {0}" + vbCrLf + " " + _
        "       Get" + vbCrLf + "            Return CType(_cl.Item(Index), {0})" + vbCrLf + "        End Get" + vbCrLf + "    En" + _
        "d Property" + vbCrLf + "" + vbCrLf + "    '''<summary>Returns a specific member of a {0}Collection object " + _
        "by key.</summary>" + vbCrLf + "    '''<remarks>If Index doesn't match any existing member of " + _
        "the collection, an error" + vbCrLf + "    '''occurs." + vbCrLf + "    '''The Item property is the default " + _
        "property for a collection. Therefore, the" + vbCrLf + "    '''following lines of code are equ" + _
        "ivalent:" + vbCrLf + "    '''$CODE$" + vbCrLf + "    '''Print MyCollection(""myKey"")" + vbCrLf + "    '''Print MyColle" + _
        "ction.Item(""myKey"")" + vbCrLf + "    '''$END-CODE$" + vbCrLf + "    '''</remarks>" + vbCrLf + "    '''<param name=""I" + _
        "ndex"">An expression that specifies the position of a member of the" + vbCrLf + "    '''colle" + _
        "ction. it must correspond to the key value specified when the member" + vbCrLf + "    '''refe" + _
        "rred to was added to the collection. </param>" + vbCrLf + "    '''<exception cref=""ArgumentE" + _
        "xception"">Index doesn't match an existing member of" + vbCrLf + "    '''the collection.</exc" + _
        "eption>" + vbCrLf + "    '''<exception cref=""IndexOutOfRangeException"">Index doesn't match " + _
        "an existing" + vbCrLf + "    '''member of the collection.</exception>" + vbCrLf + "    Default Public Over" + _
        "loads ReadOnly Property Item(ByVal Index As String) As {0}" + vbCrLf + "        Get" + vbCrLf + "         " + _
        "   Return CType(_cl.Item(Index), {0})" + vbCrLf + "        End Get" + vbCrLf + "    End Property" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "    '''" + _
        "<summary>Removes a member from a {0}Collection object.</summary>" + vbCrLf + "    '''<param n" + _
        "ame=""Key"">A unique String expression that specifies a key string that" + vbCrLf + "    '''c" + _
        "an be used, instead of a positional index, to access a member of the" + vbCrLf + "    '''coll" + _
        "ection. It must correspond to the Key argument specified when the member" + vbCrLf + "    '''" + _
        "referred to was added to the collection.</param>" + vbCrLf + "    '''<exception cref=""Argume" + _
        "ntException"">Key is invalid or not specified.</exception>" + vbCrLf + "    Public Overloads " + _
        "Sub Remove(ByVal Key As String)" + vbCrLf + "        _cl.Remove(Key)" + vbCrLf + "    End Sub" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "    '''<su" + _
        "mmary>Removes a member from a {0}Collection object.</summary>" + vbCrLf + "    '''<param name" + _
        "=""Index"">An expression that specifies the position of a member of the" + vbCrLf + "    '''c" + _
        "ollection. Index must be a number from 1 to the value of the collection's Count" + vbCrLf + "" + _
        "    '''property.</param>" + vbCrLf + "    '''<exception cref=""IndexOutOfRangeException"">Ind" + _
        "ex does not match an existing" + vbCrLf + "    '''member of the collection.</exception>" + vbCrLf + "    P" + _
        "ublic Overloads Sub Remove(ByVal Index As Integer)" + vbCrLf + "        _cl.Remove(Index)" + vbCrLf + "   " + _
        " End Sub" + vbCrLf + "" + vbCrLf + "    Public Overrides Function ToString() As String" + vbCrLf + "        Return _cl." + _
        "ToString()" + vbCrLf + "    End Function" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "#Region ""Custom enumerator""" + vbCrLf + "    '''<summary>Enum" + _
        "erator class for {0} objects.</summary>" + vbCrLf + "    '''<remarks>Inner class for strongly" + _
        " typed For Each operations.</remarks>" + vbCrLf + "    Public Class {0}Enumerator" + vbCrLf + "        Imp" + _
        "lements IEnumerator" + vbCrLf + "" + vbCrLf + "        Private position As Integer = 0" + vbCrLf + "        Private col" + _
        " As {0}Collection" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "        Public Sub New(ByVal {0}s As {0}Collection)" + vbCrLf + "        " + _
        "    Me.col = {0}s" + vbCrLf + "        End Sub" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "        Public Function MoveNext() As Boolea" + _
        "n Implements IEnumerator.MoveNext" + vbCrLf + "            If position <col.Count Then" + vbCrLf + "     " + _
        "           position += 1" + vbCrLf + "                Return True" + vbCrLf + "            Else" + vbCrLf + "          " + _
        "      Return False" + vbCrLf + "            End If" + vbCrLf + "        End Function" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "        Public Sub " + _
        "Reset() Implements IEnumerator.Reset" + vbCrLf + "            position = 0" + vbCrLf + "        End Sub" + vbCrLf + "" + vbCrLf + " " + _
        "       '''<summary>Returns current item.</summary>" + vbCrLf + "        '''<remarks>This meth" + _
        "od is only implemented to justify IEnumerator interface" + vbCrLf + "        '''implementatio" + _
        "n." + vbCrLf + "        '''</remarks>" + vbCrLf + "        Private ReadOnly Property IEnumerator_Current()" + _
        " As Object Implements IEnumerator.Current" + vbCrLf + "            Get" + vbCrLf + "                Return" + _
        " Current" + vbCrLf + "            End Get" + vbCrLf + "        End Property" + vbCrLf + "" + vbCrLf + "" + vbCrLf + "        '''<summary>Returns " + _
        "current item.</summary>" + vbCrLf + "        Public ReadOnly Property Current() As {0}" + vbCrLf + "      " + _
        "      Get" + vbCrLf + "                Return col.Item(position)" + vbCrLf + "            End Get" + vbCrLf + "        " + _
        "End Property" + vbCrLf + "    End Class" + vbCrLf + "#End Region" + vbCrLf + "End Class" + vbCrLf + ""
#End Region

If you don't know it yet, you can learn how to create and run macro. You can of course create a toolbar or menu button or assign keyboard shortcut to it.

If you want to modify generated code just modify collTemplate variable. Is it pure madness for you? Then you haven't seen my macro for pasting long text as string which will produce such nice string for you :)

Comments

# Peter Macej 2006-02-09 21:14
I forgot to mention that there is no need for this macro in VS 2005. .NET framework version 2.0 contains new Collection Generic Class which is exactly what we need.

Comments are now closed for this entry

 

Start generating your .NET documentation now!
DOWNLOAD
Free, fully functional trial