Implementing Strongly Typed Collection

A collection is the generic name used for a data structure used to store data. In Visual Basic .NET, a class named Collection, is implemented in the Microsoft.VisualBasic namespace and was ported to .NET for familiarity.

Collection objects are useful for storing heterogeneous-different-kind-data and provide a convenient way to store more than one of something. An improvement over the everyday vanilla Collection is a strongly typed collection. A strongly typed Collection is a Collection that contains a known type. For example, suppose you are writing an application to store information about books in a library. You could create a strongly typed collection of Book objects.

The first obvious benefit of a Collection is the one you get when you need to manage more than one of something. The benefit of a strongly typed collection is the one you get by knowing the type of object in the collection, and working with the items in the collection without having to typecast between the type you are really storing and the generic Object type stored in a Collection. Another benefit is that if you are writing the code that manages the Collection then you can perform any other operations on the items as they go into and come out of your custom Collection.

Traditionally a custom collection could be implemented by adding a collection to a new class and then surfacing constituent collection properties as the supporting under wire for strongly typed methods and properties. For example, if you wanted to create a strongly typed collection of Book objects in VB6 then you could add a collection to a class and add methods and properties to the new class. The new class' methods and properties would perform the type conversion between the generic type stored in the collection and the type you were storing, the Book. (There are a variety of other ways to solve the problem, too. You could rely on late binding and simply not make a strongly typed collection.)

Visual Basic .NET supports creating a strongly typed collection by inheriting from the System.Collections.CollectionBase class. The CollectionBase class implements IList, IListSource, and IEnumerable. The result is that your Visual Basic .NET strongly typed collections can be used in iteration statements, are indexable like an array, and can be bound directly to controls as a data source. For instance, you can bind a strongly typed collection to a DataGrid control in Windows and Web Forms.

In the rest of this article I will demonstrate the steps you need to perform to create a strongly typed collection.

Inheriting from CollectionBase

The CollectionBase class is defined in the System.Collections namespace. CollectionBase is an abstract-tagged with the MustInherit access specifier-class that provides a basic implementation for IList, IListSource, and IEnumerable. By generalizing the CollectionBase class you can create a strongly typed collection. (I will demonstrate in a moment.)

The IList, IListSource, and IEnumerable interfaces require consumers to implement methods and properties that support binding, enumerating, and looping using the For Each construct. IList declares methods for adding, inserting, removing, and clearing elements from a list. IListSource declares methods that return a Boolean indicating if there is a list in the object and a method that returns an object that implements IList. IEnumerable declares one method, GetEnumerator. (Enumerators are base on an interface that supports iterating the elements in a collection.) You can look all of these interfaces up in the Visual Studio .NET help for more information.

CollectionBase contains an InnerList and List properties that represent the internally stored collection of data. To inherit from the CollectionBase all you need to do is define a new class that inherits from System.Collection.CollectionBase. To make the custom, strongly typed collection useful you can implement a Default Property indexer that will return a typed object. Listing 1 contains the basic source code for implementing a strongly typed collection.

Listing 1: The shell of a strongly typed collection.

Imports System
Imports System.Collections

Public Class Books
  Inherits CollectionBase
End Class

A convention I employ is to provide a plural name for the collection based on the name of the object I will be storing in the collection. Thus the implication is that a collection named Books will contain Book objects. Listing 2 provides a simple implementation of a Book class.

Listing 2: A basic implementation of an object used to store information about Books.

Public Class Book
  Private FTitle As String
  Private FAuthor As String

  Public Sub New(ByVal title As String, ByVal author As String)
    FAuthor = author
    FTitle = title
  End Sub

  Public Property Author() As String
    Get
      Return FAuthor
    End Get
    Set(ByVal Value As String)
      FAuthor = Value
    End Set
  End Property

  Public Property Title() As String
    Get
      Return FTitle
    End Get
    Set(ByVal Value As String)
      FTitle = Value
    End Set
  End Property

End Class

The Book class is defined to contain a reference to a single book, including the Author and Title information.

Implementing a Default Property

To make the Books collection more convenient we can implement an indexed property and specify that the indexer is the Default property. The Books collection from listing 1 is revised as shown in listing 3.

Listing 3: Adding a Default indexed property to our strongly typed collection.

Public Class Books
  Inherits CollectionBase

  Default Public Property Item(ByVal Index As Integer) As Book
    Get
      Return CType(List.Item(Index), Book)
    End Get
    Set(ByVal Value As Book)
      List.Item(Index) = Value
    End Set
  End Property

End Class

By convention we name indexed properties in Visual Basic .NET Item. By implementing the Item property we can index an instance of Books as follows:

Dim MyBooks As Books = New Books()
MyBooks.Item(0).Author = "Paul Kimmel"

Assuming there are books in MyBooks, we can change the author information by indexing the underlying array and accessing a property of the contained type, Book. By specifying that Item is the Default property we can shorten the access to the elements in Books by treating MyBooks as an array. Here is the revision.

Dim MyBooks As Books = New Books()
MyBooks(0).Title = "Sams Visual Basic .NET Unleashed"

Notice that I am not required to use the Books.Item property explicitly. The behavior of Books can be expressed by rewriting the code verbosely (although we would never do this in an application.)

Dim MyBooks As Books
MyBooks = New Books()
Dim ABook As Book
ABook = MyBooks.Item(0)
ABook.Author = "Robert Phillips"

(Verbose code is useful for instructional purposes but otherwise is just extra work.) The preceding examples demonstrate how to access the strongly typed Book objects in the Books collection, but how do we get strongly typed objects into Books in the first place. One way is to implement a public method, Add, that takes a Book argument and inserts the argument into the internal List.

Implementing an Add Method for Serialization

You are not required to implement an Add method for a strongly typed collection. Of course, you can implement Add or any other method, which is part of the reason you would create a custom class in the first place. However, you must implement an Add method if you plan on serializing your strongly typed collection. (See "Serializing Objects into a DataSet" in the second half of this article in May.)

The basic Add method for our collection takes a Book argument and returns an Integer. Listing 4 provides a basic implementation of an Add method added to our Books collection.

Listing 4: Adding an Add method to the strongly typed collection.

Imports System
Imports System.Collections

Public Class Books
  Inherits CollectionBase

  Default Public Property Item(ByVal Index As Integer) As Book
    Get
      Return CType(List.Item(Index), Book)
    End Get
    Set(ByVal Value As Book)
      List.Item(Index) = Value
    End Set
  End Property

  Public Function Add(ByVal Item As Book) As Integer
    Return List.Add(Item)
  End Function

End Class

Note: Why is the Add method necessary for Serialization? I have to guess here because I don't have the Common Language Runtime code-there is a freeware product Anakrino that allows you to explore the CLR, but this is not the same as Microsoft's source. When an object is deserialized the default constructor is called. This is consistent and easy. After the object is instantiated, Reflection is used to find all of the properties so that the serialized data can be assigned back to the properties of the reconstituted object. Finally, the reconstituted objects are added to the strongly typed collection; hence the Add method.

The Add method accepts a Book and inserts it into the List we inherited from CollectionBase. We can add data to our collection and use it just like an array too.

Binding the Collection to a DataGrid

To use our collection we can create an instance of the Books object, add several Book objects to it, and bind the collection controls. The Windows Forms DataGrid is a good test control. (Refer to figure 1 for an example of the test program.)

*** IMG ***

Figure 1: Shows the Books collection bound to the DataGrid.

The source code for adding elements to Books and binding to the DataGrid is shown in listing 4.

Listing 4: Test the Books collection.

Dim B As Books = New Books()
B.Add(New Book("Sams Visual Basic .NET Unleashed", "Paul Kimmel"))
B.Add(New Book("Design Patterns", "Erich Gamma et. al."))
B.Add(New Book("The CRC Card Book", "David Bellin"))
B.Add(New Book("Visal Basic .NET: Tips, Tricks, and Tutorials", _
         "Paul Kimmel et. al."))

DataGrid1.DataSource = B

That's all there is to it. Check back May 15th for the second half of this article. I will demonstrate how you can use the XmlSerializer to stream an array of objects directly into an ADO.NET DataSet.

Summary

One of the lessons that we have learned in the last twenty years is that code must be expressive and convey meaning. A type that holds any kind of object is useful, but a type that holds a specific type is reliable. Building reliable software means that we have to use strongly typed objects that explicitly manage the way our software behaves.

You now know the recommended way to create a strongly typed collection of objects. Add it to your toolbox. Next week I will be demonstrating how to serialize objects right into a DataSet. If there are subjects that you are interested in then send me an email.

About the Author

Paul Kimmel is a freelance writer for Developer.com and CodeGuru.com. Look for his recent book, Visual Basic .Net Unleashed, at a bookstore near you. Paul Kimmel is available to help design and build your .NET solutions and can be contacted at pkimmel@softconcepts.com.