You may think of generics as a Ferrari that you only take out for special occasions; but they are better compared to your trusty pickup, perfectly suited for everyday use.

It would be nice to begin by saying in simple terms what generic are … but it is hard to explain exactly what generics are. Generics are not really a single thing but rather a set of techniques with a single purpose: to work with generalized code in a data type-specific way.

With the built-in generic collection classes you may never need to dimension an array again!

This somewhat complex-sounding purpose is why it may appear that generics are a Ferrari-style technique. But getting past the definition to actual examples will demonstrate how you can use generics every day to minimize the amount of code you write and improve the performance of your applications.

The two most fundamental and potentially “everyday” generics techniques involve the built-in generics collections and generic methods.

If you ever use arrays or ArrayLists in your applications, consider using the built-in generics collections instead. The built-in generics collections are not only easier to use than arrays, but they allow you to limit the data type of the items that are in the collection. This provides type-safety, meaning that a compile-time error is generated if the code attempts to put something in the collection that is not of the correct type. It can also improve the performance of the application, limiting conversion of data types.

Generic methods provide a way for you to write code that can work with any data type. You then specify the data type that you want to use when you call the code. This minimizes repetitive code and maximizes type safety.

For both techniques, generics provide for code (either that you write or that is in the Framework) that will work with any data type, such as strings, integers, business objects, etc. You then specify the data type that the code should use when you call that code. That allows you to reuse that code for any number of data types.

This article demonstrates how to use the built-in generic collections instead of arrays. It then details other uses of generic collections, such as for data binding and sorting. Finally, it describes how to build your own generic methods to take advantage of generics every day.

Using Generics Instead of Arrays

Arrays are a common mechanism for storing data in an application. You may currently use them to store internal data or user-entered values. But working with arrays is somewhat complex. First you need to set up their dimension, and then you need to keep track of how many items you are adding to the array so you don’t exceed their dimension.

For example, the following code takes user-entered names and adds them to an array:

Private NameArray(5) As String
Private NameArrayIndex As Integer = 0
    
…
    
If NameArrayIndex > 5 Then
    ReDim Preserve NameArray(NameArray.Length + 5)
End If
NameArray(NameArrayIndex) = NameTextBox.Text
NameArrayIndex += 1

If you instead use one of the built-in generic collections, you can replace the two declarations with one and replace the five lines of code to manage the array with one line of code:

Private NameList As New List(Of String)
    
…
    
NameList.Add(NameTextBox.Text)

Note: If Visual Studio does not recognize the List keyword, examine your project imports (My Project, References Tab, Imported Namespaces section). Ensure that System.Collections.Generic is checked. Or prefix the List keyword with System.Collections.Generic.

The declaration in this example creates a new instance of the List built-in generic collection. The “Of String” syntax is used to identify the type parameter and defines the specific data type to use with the generic collection. In this case, only strings can be added to the collection.

Note: You will often see generics documented using “Of T” such as List(Of T). The T is the placeholder for the type parameter and can be replaced with any data type when using the generics collection or method.

If the code attempts to put something other than a string into the collection, the code generates a compile-time error. For example:

NameList.Add(1)

This line will generate the error: “Option Strict On disallows implicit conversions from 'Integer' to 'String'”. (Assuming of course that you have Option Strict On.)

If you want to retain key and value pairs, such as U.S. states along with their abbreviations, you can use a generic Dictionary. Generic Dictionaries have two type parameters, a unique key and a value.

Note: Do not confuse these generic Dictionaries with the old scripting Dictionaries sometimes used in Visual Basic 6.0 applications.

The following code creates a new generic Dictionary and adds the abbreviation as the key and the long name as the value:

Private StateDictionary As New Dictionary(Of
String, String)
    
…
    
StateDictionary.Add("CA", "California")
StateDictionary.Add("NY", "New York")
StateDictionary.Add("WI", "Wisconsin")
…

Even though the code examples so far showed Strings as the type parameters, you can use any data type as the key or as the value.

For example, if you had a Person business object with a unique numeric ID, you could define a PersonDictionary as follows:

Private PersonDictionary As New Dictionary(Of
Integer, Person)

The .NET Framework provides for several different types of generic collections. The ones that you may use most often include:

  • Dictionary: Provides a list of key and value pairs where the key must be unique. For example PersonDictionary(Of Integer, Of Person) represents a collection of Person objects keyed by a unique integer value.
  • List: Provides a list of values that can be accessed by index. For example PersonList(Of Person) represents a list of Person objects. The List provides methods to search, sort, and manipulate lists.
  • SortedDictionary: Provides a generic Dictionary that is sorted by the unique key.
  • SortedList: Provides a generic List that is sorted by a unique key.

Each of these generic collections provides better type safety and performance than non-generic collections and is much easier to use than arrays. With the built-in generic collection classes you may never need to dimension an array again!

Data Binding With Generic Collections

One common everyday programming task is to display data to the user. Generic collections along with the latest data binding features in Visual Studio 2005 provide a quick and easy way to display data to the user.

Generic collections along with the latest data binding features provide a quick and easy way to display data to the user.

To try this, create a new form. Add a TextBox named NameTextBox, a button named AddButton, and a ListBox named NamesListBox to the form. Insert the following declaration into the form code:

Private NameList As New List(Of String)

In the Click event for the button, add the following code:

NameList.Add(NameTextBox.Text)
NameTextBox.Text = String.Empty
    
NamesListBox.DataSource = Nothing
NamesListBox.DataSource = NameList

The first line adds the value entered into the textbox to the List. The second line clears the textbox for entry of another value. The last two lines set up the binding.

Note: The DataSource did not seem to reset to the latest information from the List unless it was first set to Nothing.

Run the resulting application. Type a name and click Add. The name should appear in the ListBox. Repeat this step to add each name to the ListBox.

Next you’ll display the data in sorted order using one of these techniques: (1) Sort the generic List. (2) Use a generic SortedList. Both ways have pros and cons.

To sort the List, add a button named SortButton to the form and add the following code to the Click event for that button:

NameList.Sort()
    
NamesListBox.DataSource = Nothing
NamesListBox.DataSource = NameList

The first line performs the sort and the second two lines rebind the ListBox. This is necessary to ensure the ListBox contents reflect the sorted data.

Run the application. Type a name and click Add. Repeat this several times to create a list of names that are in no particular order. Now click Sort to view the sorted list.

The Sort method will perform an ascending sort on the contents of the List, assuming that the data type of the List has a default sort comparer. (A comparer is used to compare two values, which is needed when sorting to determine whether an item should be sorted ahead of or behind another item.) Strings and other basic data types have a default sort comparer. Once the list is sorted, you can reverse the sort order using the Reverse method of the List.

If you then add items to the List after it has been sorted, any newly added items will not appear in sorted order. You will need to resort after adding each item.

To define a list that will remain sorted as you add items, use the generic SortedList instead. To try this out, change the declaration of the List(of T) to be SortedList(Of TKey, TValue):

Private NameList As New SortedList(Of String,
String)

The SortedList sorts on a unique key value, so it requires two type parameters. The first type parameter is the type of the unique key. The second parameter is the value.

The unique key requirement is the primary con of using the SortedList: you must assign a unique key to every value and you can only sort on that key (not on the value). However, the positive aspect of a SortedList is that it stays sorted. You don’t need to resort after adding each entry.

To continue trying out the SortedList, you need to change the code that adds items to the List to assign a key as well. For example:

NameList.Add(NameTextBox.Text, NameTextBox.Text)
NameTextBox.Text = String.Empty
    
NamesListBox.DisplayMember = "Value"
NamesListBox.DataSource = New
BindingSource(NameList, Nothing)

This example uses the same text as the key and the value. Notice how the data binding code is also changed in this code. The NameList now has a Key and a Value, so you need to define which will be displayed in the list using the DisplayMember property of the ListBox. And since the binding is more complex, you can no longer simply assign the NameList as the DataSource. You must instead create a new BindingSource, passing the NameList as a parameter.

Since the keys must be unique, the example will generate an error if the user types in a duplicate entry, so additional exception handling is required:

If NameList.ContainsKey(NameTextBox.Text) Then
   MessageBox.Show("The entered item is already
   on the list")
Else
   NameList.Add(NameTextBox.Text,
   NameTextBox.Text)
   NameTextBox.Text = String.Empty
 
   NamesListBox.DisplayMember = "Value"
   NamesListBox.DataSource = New
   BindingSource(NameList, Nothing)
End If

Run the application. Type a name and click Add. Repeat this several times and you will notice that the ListBox remains sorted. Type in a name that already exists and you should see your error message.

The default comparer for the SortedList sorts in ascending order. If you want to sort in descending order, you need to write your own generic comparer method as described in the next section.

Use data binding to display the contents of a List to the user as a ListBox or ComboBox. Use the Sort and Reverse methods of the List if you want to sort on possible repeating values. Use the SortedList instead if you want the list to sort automatically as you add values, you can assign a unique key to every value, and you want the List sorted by that key and not the value.

Writing Generic Methods

Generics provide a way for you to write code that can work with any data type, such as strings, integers, business objects, etc. You then specify the data type that you want to use when you call the method.

By writing a generic method, you can write code one time, and use in with every form and with any business object.

Since writing comparers for sorting is not such an everyday activity, let’s wait that until the end of this section. Let’s start with more common functionality, such as a Save option on a form.

To really see the benefit of writing generic methods, the sample application will need to have a more complex data type, not just strings or integers (though strings and integers will of course work). Plus it will need more than one type to demonstrate how the generic method works with multiple types. The sample presented here creates Person and Task classes along with forms for data entry of person and task information.

Start by creating a business object. For this example, build a Person class with properties for LastName and FirstName:

Private _LastName As String
Public Property LastName() As String
   Get
      Return _LastName
   End Get
   Set(ByVal value As String)
      _LastName = value
   End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
   Get
      Return _FirstName
   End Get
   Set(ByVal value As String)
      _FirstName = value
   End Set
End Property

Add a Save method to the Person class. For purposes of this example, it does not need to access a database and actually save the values, just display a message.

Public Sub Save()
   ' just to see how this works…
   MsgBox("Got to the Save: " & LastName)
End Sub

Note: Since this is in a class and not a Windows form, you cannot use MessageBox.Show unless you have a reference to System.Windows.Forms. And we all know that business objects should not be displaying messages-this is just a sample.

Create a form to allow users to enter person information with TextBoxes for LastName and FirstName. Include a button named AddButton to add the person to the List and a button named SaveButton to perform a save operation. If desired, add a grid to display the contents of the List.

Change the declaration section of the form code to read as follows:

Private PersonList As New List(Of Person)

The code in the Click event for the Add button is as follows:

Dim newPerson As New Person
With newPerson
   .LastName = LastNameTextBox.Text
   .FirstName = FirstNameTextBox.Text
End With
    
' Add the person to the collection
PersonList.Add(newPerson)
    
' Clear the form for the next entry
FirstNameTextBox.Text = String.Empty
LastNameTextBox.Text = String.Empty

This code creates a new Person object, populates it from the TextBox values, and adds it to the List. It then clears the TextBox values for entry of another person’s data.

Note: You cannot easily bind the List(Of Person) to a grid because you need to map individual columns of the grid to specific Person object properties. You can either perform this mapping manually; populating the grid with code, or you can use object binding to bind the grid. Also, if you use object binding, you can automatically assign the properties of the person object to the TextBox values so lines 2-5 of the above example could be deleted. See my article, “Object Binding Tips and Tricks” in the March/April 2006 issue of CoDe Magazine for more information.

Repeat the process above for a Task object, creating a Task class with properties such as TaskName and Description and a Save method. Add a form for entry of task information with code similar to the data entry form for person data. If you want to save a few steps, you can download the code for this article from the CoDe Magazine Web site.

At this point, think about the things that your application normally needs to do during the save process that are the same for both tasks and persons. For example, before executing the save you may want to ensure all required validation is performed, display a “saving” message, or other pre-processing. Then the code needs to call the business object to perform the unique processing, such as calling the appropriate save stored procedure. Finally, you may have some post-processing in the form, such as displaying a “save complete” message or disabling the Save button until something else is changed.

You may be writing this code now in every form. By writing a generic method, you can write code one time and use in with every form and with any business object.

To easily use the code in every form, create a base form class. If you haven’t spent a lot of time with form classes, see my article, “Give Your Forms a Base” in the March/April 2004 issue of CoDe Magazine.

Ensure every form then inherits from this base form class instead of System.Forms.Form by changing the code in the .Designer.vb file as follows:

Partial Class GenericMethodExample
    Inherits BaseWin

Then add the generic save method to the base form class:

Protected Sub Save(Of T)(ByVal objectToSave As T)
   ' Do any pre-processing
    
   ' Call the appropriate save method on the
   ' business object
   Dim _methodInfo As Reflection.MethodInfo
   _methodInfo = GetType(T).GetMethod("Save")
   _methodInfo.Invoke(objectToSave, Nothing)
    
   ' Do any post processing
End Sub

The first thing that you will notice is that the generic method is declared slightly differently. Use the “(Of T)” syntax to indicate that the save method is a generic method. All generic methods require this syntax. You can define any number of type parameters, such as (Of TSource, TDestination). Whatever type parameter variable(s) are used in the method declaration can then be used throughout the method to represent that type.

Note: Though most standards suggest using T or T with a suffix, such as TKey or TValue, you can actually use any letters for the type parameter placeholder.

The method parameter in this example (objectToSave) is also declared to be “As T”, indicating that an object of the desired type must be passed to this method.

The first lines of code in this method would be your code for any processing required before actually saving the data. For example, this code could include a call to validation or display of a status message.

This method then uses reflection to call the appropriate Save method on the business object. It uses GetMethod to find the method on the defined business object and then invokes that method.

Any post processing is next, such as displaying a save complete status message or disabling the save button until the user makes another change.

Calling the method is easy. In the Click event for the Save button on each form, add code similar to the following:

For Each individual As Person In PersonList
   MyBase.Save(individual)
Next

This example calls the Save method in the base form class for each person in the List.

The code for tasks is as follows:

For Each item As Task In TaskList
   MyBase.Save(item)
Next

By building generic methods, you can write generalized code that is type safe and works with any business object in your application.

Now that you know how to build a generic method, let’s look at the comparer, which not only requires a generic method, but a generic class as well. Listing 1 shows the code for a simple comparer class:

This code defines a generic PropertyComparer class. It uses the “(Of T)” syntax to denote that it is a generic class. It implements the IComparer generic interface to support comparing two values.

The class has two fields: _propertyInfo and _sortDirection. _propertyInfo retains information on the property of the object selected for the sort. This allows you to sort on any business object property. _sortDirection defines an ascending or descending sort.

The constructor takes the property name as a string and the sort direction and uses reflection to get information on the property based on the property name.

The generic Compare method is called automatically during a sort operation to compare the values and sort them appropriately. This Compare method calls the default comparer for the defined property type. For example, it will call the String comparer for properties of type String.

Once the PropertyComparer code is in place, performing the sort using the PropertyComparer class is easy. To sort the TaskList, for example, you use this code:

TaskList.Sort(New PropertyComparer(Of _
Task)("TaskName", SortOrder.Ascending))

The built-in Sort method of the generic List takes a comparer as a parameter. In this example, the Sort method creates a new PropertyComparer for the Task business object. It then passes in the name of the property of the Task business object to use for the sort, in this case the TaskName property. It also defines the sort order. The TaskList is then sorted as defined.

Create your own PropertyComparer class whenever you want to sort on a particular business property or when the default sorting features don’t provide all of the power that you need. By creating this class in your base form class or code library, you can easily reuse it for any business object.

Refactor your methods to generic methods if you find similar methods that are different only in the data type that they use. You will greatly minimize the amount of code that you need to write for common functionality.

Conclusion

Start using generics every day.

Use one of the built-in generic collections instead of an array or non-generic collection to minimize the amount of code you need to write and maximize the performance of the application.

Whenever you find yourself writing similar code for different data types, consider whether a generic method would do it with less code.

Generics are not just special occasion gear; they should be a key part of your everyday toolbox.