Wrox Home  
Search
Professional .NET 2.0 Generics
by Tod Golding
October 2005, Paperback


Excerpt from Professional .NET 2.0 Generics

Understanding Generic Classes

By Tod Golding

Many developers will view themselves primarily as consumers of generics. However, as you get more comfortable with generics, you're likely to find yourself introducing your own generic classes and frameworks. Before you can make that leap, though, you'll need to get comfortable with all the syntactic mutations that come along with creating your own generic classes. Fortunately, you'll notice that the syntax rules for defining generic classes follow many of the same patterns you've already grown accustomed to with non-generic types. So, although there are certainly plenty of new generic concepts you'll need to absorb, you're likely to find it quite easy to make the transition to writing your own generic types.

Parameterizing Types

In a very general sense, a generic class is really just a class that accepts parameters. As such, a generic class really ends up representing more of an abstract blueprint for a type that will, ultimately, be used in the construction of one or more specific types at run-time. This is one area where, I believe, the C++ term templates actually provides developers with a better conceptual model. This term conjures up a clearer metaphor for how the type parameters of a generic class serve as placeholders that get replaced by actual data types when a generic class is constructed. Of course, as you might expect, this same term also brings with it some conceptual inaccuracies that don't precisely match generics.

The idea of parameterizing your classes shouldn't seem all that foreign. In reality, the mindset behind parameterizing a class is not all that different than the rationale you would use for parameterizing a method in one of your existing classes. The goals in both scenarios are conceptually very similar. For example, suppose you had the following method in one of your classes that was used to locate all retired employees that had an age that was greater than or equal to the passed-in parameter (minAge):

[VB code]
Public Function LookupRetiredEmployees(ByVal minAge As Integer) As IList
    Dim retVal As New ArrayList
    For Each emp As Employee In masterEmployeeCollection
        If ((emp.Age >= minAge) And (emp.Status = EmpStatus.Retired)) Then
            retVal.Add(emp)
        End If
    Next
    Return retVal
End Function
[C# code]
public IList LookupRetiredEmployees(int minAge) {
    IList retVal = new ArrayList();
    foreach (Employee emp in masterEmployeeCollection) {
        if ((emp.Age >= minAge) && (emp.Status == EmpStatus.Retired))
            retVal.Add(emp);
        }
        return retVal;
    }
}

Now, at some point, you happen to identify a handful of additional methods that are providing similar functionality. Each of these methods only varies based on the status (Retired, Active, and so on) of the employees being processed. This represents an obvious opportunity to refactor through parameterization. By adding status as a parameter to this method, you can make it much more versatile and eliminate the need for all the separate implementations. This is something you've likely done. It's a simple, common flavor of refactoring that happens every day.

So, with this example in mind, you can imagine applying this same mentality to your classes. Classes, like methods, can now be viewed as being further generalized through the use of type parameters. To better grasp this concept, let's go ahead and build a non-generic class that will be your candidate for further generalization:

[VB code]
Public Class CustomerStack
    Private _items() as Customer
    Private _count as Integer

    Public Sub Push(item as Customer) 
        ...
    End Sub

    Public Function Pop() as Customer 
        ...
    End Function
End Class
[C# code]
public class CustomerStack {
    private Customer[] _items;
    private int _count;

    public void Push(Customer item) {...}
    public Customer Pop() {...}
}

This is the classic implementation of a type-safe stack that has been created to contain collections of Customers. There's nothing spectacular about it. But, as should be apparent by now, this class is the perfect candidate to be refactored with generics. To make your stack generic, you simply need to add a type parameter (T in this example) to your type and replace all of your references to the Customer with the name of your generic type parameter. The result would appear as follows:

[VB code]
Public Class Stack(Of T)
    Private _items() as T
    Private _count as Integer

    Public Sub Push(item as T) 
        ...
    End Sub

    Public Function Pop() as T 
        ...
    End Function
End Class
[C# code]
public class Stack<T> {
    private T[] _items;
    private int _count;

    public void Push(T item) {...}
    public T Pop() {...}
}

Pretty simple. It's really not all that different than adding a parameter to a method. It's as if generics have just allowed you to widen the scope of what can be parameterized to include classes.