Chapter 30. Making Generic Classes

The section on collection classes in Lesson 16 explained how to use generic collection classes. For example, the following code defines a list that holds Employee objects:

Public Employees As New List(Of Employee)

This list can hold Employee objects only, and when you get an object out of the list, it has the Employee type instead of the less specific Object type.

Lesson 16 also described the main advantages of generic classes: code reuse and specific type checking. You can use the same generic List(Of ...) class to hold a list of Strings, Doubles, or Person objects. By requiring a specific data type, the class prevents you from accidentally adding an Employee object to a list of Order objects, and when you get an object from the list you know it is an Order.

In this lesson, you learn how to build your own generic classes so you can raise code reuse to a whole new level.

Note

Many other things can be generic. You can probably guess that you can build generic structures because structures are so similar to classes. You can also build generic methods (in either generic or nongeneric classes), interfaces, delegate types, and so on. This lesson focuses on generic classes.

DEFINING GENERIC CLASSES

A generic class declaration looks a lot like a normal class declaration with one or more generic type variables added in parentheses. For example, the following code shows the basic declaration for a generic TreeNode class:

Public Class TreeNode(Of T)
    ...
End Class

The (Of T) means the class takes one type parameter, T. Within the class's code, the type T means whatever type the program used when creating the instance of the class. For example, the following code declares a variable named rootNode that is a TreeNode that handles strings:

Dim rootNode As New TreeNode(Of String)()

Note

A generic class's type parameter is analogous to a parameter passed into a subroutine or function. In this example, T is the name by which the type is known within the class, just as a parameter list gives the name by which a parameter is known within a subroutine.

If you want the class to use multiple type parameters, separate them with commas. For example, suppose you want to make a Matcher class that takes two kinds of objects and matches objects of the two kinds. It might match Employee objects with Job objects to assign employees to jobs. The following code shows how you might declare the Matcher class:

Public Class Matcher(Of T1, T2)
    ...
End Class

The following code shows how you might create an instance of the class to match Employees with Jobs:

Dim jobAssigner As New Matcher(Of Employee, Job)

Note

Many developers use T for the name of the type in generic classes that take only one type.

If the class takes more than one type, you should try to use more descriptive names so it's easy to tell the types apart. For example, the generic Dictionary class has two type variables named TKey and TValue that represent the types of the keys and values that the Dictionary will hold.

Inside the class's code, you can use the types freely. For example, the following code shows more of the TreeNode class's code. A TreeNode object represents a node in a tree, with an associated piece of data attached to it. The places where the class uses the data type T are highlighted in bold.

Public Class TreeNode(Of T
    ' This node's data.
    Public Property Data As T

    ' This node's children.
    Private Children As New List(Of TreeNode(Of T)()

    ' Initializing constructor.
    Public Sub New(ByVal newData As T
        Data = newData
    End Sub
' Override ToString to display the data.
    Public Overrides Function ToString() As String
        If Data Is Nothing Then Return ""
        Return Data.ToString()
    End Function

    ...
End Class

Notice how the class uses the type T throughout its code. The class starts with a Data property of type T. This is the data (of whatever data type) associated with the node.

Each node also has a list of child nodes that should be other TreeNodes of the same kind as this one. To hold the right kind of objects, the Children variable is a generic List(Of TreeNode(Of T)), meaning it can hold only TreeNode(Of T) objects.

The class's constructor takes a parameter of type T and saves it in the object's Data property.

To make displaying a TreeNode easier, the class overrides its ToString method so it calls the ToString method provided by the Data object. For example, if the object is a TreeNode(Of String), then this simply returns the string's value.

USING GENERIC CONSTRAINTS

The previous example overrides the TreeNode class's ToString method so it calls the Data object's ToString method. Fortunately, all objects inherit a ToString method from the Object ancestor class so you know this is possible, but what if you want to call some other method provided by the object?

For example, suppose you want to create a new instance of type T by using a parameterless constructor. How do you know type T provides a parameterless constructor? What if you want to compare two objects of type T to see which is greater? Is that possible? Or what if you want to compare two type T objects to see if they are the same (an important test for the Dictionary class)? How do you know whether two type T objects are comparable?

You can use generic constraints to require that the types used by the program meet certain criteria such as comparability or providing a parameterless constructor.

To use a generic constraint, follow the type with the keyword As and the constraint. Some typical constraints include the following:

  • A class from which the type must inherit

  • An interface (or interfaces) that the type must implement

  • New, to indicate that the type must provide a parameterless constructor

  • Structure, to indicate that the type must be a value type such as the built-in value types (Integer, Boolean) or a structure

  • Class, to indicate that the type must be a reference type

You can use multiple constraints surrounded with braces and separated by commas.

For example, the following code defines the generic Matcher class, which takes two generic type parameters T1 and T2. (To keep things simple, this code skips important error handling such as checking for null values.)

Public Class Matcher(Of T1 As {IComparable(Of T2), New}, T2 As New)
    Private Sub Test()
        Dim object1 As New T1()
        Dim object2 As New T2()
        ...
        If object1.CompareTo(object2) < 0 Then
            ' The object1 is "less than" object2.
            ...
        End If
    End Sub
    ...
End Class

The first constraint requires that type parameter T1 implement the generic IComparable interface for the type T2 so the code can compare T1 objects to T2 objects. The next constraint requires that the T1 type also provide a parameterless constructor. You can see that the code creates a new T1 object and uses its CompareTo method (which is defined by IComparable).

The second constraint clause requires that the type T2 also provide a parameterless constructor. The code needs that because it also creates a new T2 instance.

Note

In general you should use as few constraints as possible because that makes your class usable in as many circumstances as possible. If your code won't need to create new instances of a data type, don't use the New constraint. If your code won't need to compare objects, don't use the IComparable constraint.

MAKING GENERIC METHODS

In addition to building generic classes, you can also build generic methods inside either a generic class or a regular nongeneric class.

For example, suppose you want to rearrange the items in a list so the new order alternately picks items from each end of the list. If the list originally contains the numbers 1, 2, 3, 4, 5, 6, then the alternated list contains 1, 6, 2, 5, 3, 4.

The following code shows how a program could declare an Alternate function to return an alternated list. Note that you could put this function in any class, generic or not.

Public Function Alternate(Of T)(ByVal values As List(Of T)) As List(Of T)
    ' Make a new list to hold the results.
    Dim alternatedItems As New List(Of T)()
    ...
    Return alternatedItems
End Function

The Alternate function takes a generic type parameter T. It takes as a regular parameter a List that holds items of type T and it returns a new List containing items of type T.

This snippet creates a new List(Of T) to hold the results. (Note that it does not need to require the type T to have a default constructor because the code is creating a new List, not a new T.) The code then builds the new list (not shown here) and returns it.

The following code shows how a program might use this function:

Dim names As New List(Of String)(
    {"Ant", "Badger", "Cat", "Dog", "Eagle", "Frog"}
)
Dim alternatedNames As List(Of String) = Alternate(Of String)(names)

The first statement defines a List(Of String) and initializes it with the some strings.

The second statement calls Alternate(Of String) to create an alternated List(Of String). Notice how the code uses (Of String) to indicate the data type that Alternate will manipulate. (This is actually optional and the program will figure out which version of Alternate to use if you omit it. However, this makes the code more explicit and may catch a bug if you try to alternate a list containing something unexpected such as Person objects.)

Generic methods can be quite useful for the same reasons that generic classes are. They enable code reuse without the extra hassle of converting values to and from the nonspecific Object class. They also perform type checking, so in this example, the program cannot try to alternate a List(Of Integer) by calling Alternate(Of String).

TRY IT

In this Try It, you build a generic Randomize function that randomizes an array of objects of any type. To make it easy to add the function to any project, you define the function in a code module.

Note

You can download the code and resources for this Try It from the book's web page at www.wrox.com or www.vb-helper.com/24hourvb.html. You can find them in the Lesson30 folder of the download.

Lesson Requirements

In this lesson:

  • Start a new project and give it a code module named ArrayFunctions.

  • Create a generic Randomize function with one generic type parameter T. The function should take as a parameter an array of T and return an array of T.

  • Make the main program test the function.

Hints

  • Use the following code for the function's body. Try to figure out the function's declaration yourself before you read the step-by-step instructions that follow.

    ' Make a Random object to use to pick random items.
    Dim rand As New Random()
    
    ' Make a copy of the array so we don't mess up the original.
    Dim randomizedItems() As T = DirectCast(items.Clone(), T())
    
    ' For each spot in the array, pick
    ' a random item to swap into that spot.
    For i As Integer = 0 To items.Length - 2
        ' Pick a random item j between i and the last item.
        Dim j As Integer = rand.Next(i, items.Length)
    
        ' Swap item j into position i.
        Dim temp As T = randomizedItems(i)
        randomizedItems(i) = randomizedItems(j)
        randomizedItems(j) = temp
    Next i
    
    ' Return the randomized array.
    Return randomizedItems

Step-by-Step

  • Start a new project and give it a code module named ArrayFunctions.

    1. This is reasonably straightforward.

  • Create a generic Randomize function with one generic type parameter T. The function should take as a parameter an array of T and return an array of T.

    1. The following code shows how you can implement this function:

      ' Randomize the items in an array.
      Public Function Randomize(Of T)(ByVal items() As T) As T()
          ' Make a Random object to use to pick random items.
          Dim rand As New Random()
      
          ' Make a copy of the array so we don't mess up the original.
          Dim randomizedItems() As T = DirectCast(items.Clone(), T())
      
          ' For each spot in the array, pick
          ' a random item to swap into that spot.
          For i As Integer = 0 To items.Length - 2
              ' Pick a random item j between i and the last item.
              Dim j As Integer = rand.Next(i, items.Length)
      
              ' Swap item j into position i.
              Dim temp As T = randomizedItems(i)
              randomizedItems(i) = randomizedItems(j)
              randomizedItems(j) = temp
          Next i
      ' Return the randomized array.
          Return randomizedItems
      End Function
  • Make the main program test the function.

    1. The program I wrote uses two TextBoxes, one to hold the original items and one to display the randomized items. When you click the Randomize button, the following code executes:

      ' Randomize the list and display the results.
      Private Sub btnRandomize_Click() Handles btnRandomize.Click
          ' Get the items as an array of strings.
          Dim items() As String = txtItems.Lines
      
          ' Randomize the array.
          Dim randomizedItems() As String = Randomize(Of String)(items)
      
          ' Display the result.
          txtRandomized.Lines = randomizedItems
      End Sub

      Notice that the code uses the TextBox's Lines property to get the entered values. That property returns the lines in a multiline TextBox as an array of strings.

Note

Please select Lesson 30 on the DVD to view the video that accompanies this lesson.

EXERCISES

  1. Finish building the generic Alternate function described earlier in this lesson. Add the code needed to make the alternating version of the list. To make using the function easy, add it to the ArrayFunctions module you built for the Try It. Make the main program test the method with lists containing odd and even numbers of items.

  2. Modify the TreeNode class described earlier in this lesson. Give it X and Y properties and add them to the initializing constructor.

    Give the class a DrawLinks method that takes as a parameter a Graphics object and draws lines between the node and its children. It should then recursively call its children's DrawLinks methods to make them draw their links.

    Also give the class a DrawNode method that takes a Graphics object and a Font as parameters. It should draw a circle centered at (X, Y) and then draw the node's value inside it. You can use code similar to the following:

    ' Draw the node itself.
    Public Sub DrawNode(ByVal gr As Graphics, ByVal theFont As Font)
        ' Recursively draw the children.
        For Each child As TreeNode(Of T) In Children
            ' Draw the child.
    child.DrawNode(gr, theFont)
        Next child
    
        ' Draw this node.
        Using string_format As New StringFormat()
            ' Draw a circle.
            gr.FillEllipse(Brushes.White, X - 10, Y - 10, 20, 20)
            gr.DrawEllipse(Pens.Black, X - 10, Y - 10, 20, 20)
    
            ' Draw the text.
            string_format.Alignment = StringAlignment.Center
            string_format.LineAlignment = StringAlignment.Center
            gr.DrawString(ToString(), theFont, Brushes.Green,
                X, Y, string_format)
        End Using
    End Sub

    Make the main program build a tree and draw it in its Paint event handler. Figure 30-1 shows an example solution.

    FIGURE 30-1

    Figure 30.1. FIGURE 30-1

  3. Make a generic PriorityQueue class. The class is basically a list holding generic items, with each item having an associated priority. Give the class a nested ItemData structure similar to the following to hold an item. This structure is defined inside the PriorityQueue class and won't be used outside of the class, so it can be Private. Note that this structure uses the class's generic type parameter T for the data it holds.

    ' A structure to hold items.
    Private Structure ItemData
        Public Property ItemPriority As Integer
        Public Property Data As T
    End Structure

    The class should store its ItemData objects in a generic List.

    Give the class a read-only public Count property that returns the number of items in the list.

    Give the class an AddItem method that takes as parameters a piece of data and a priority. It should make a new ItemData to hold these values and add it to the list.

    Finally, give the class a GetItem method that searches the list to find the item with the smallest priority number (priority 1 means top priority), removes that item from the list, and returns the item and its priority via parameters passed by reference. (If there's a tie for lowest priority number, return the first item found with that priority.)

Note

You can find solutions to this lesson's exercises in the Lesson30 folder inside the download available on the book's web site at www.wrox.com or www.vb-helper.com/24hourvb.html.

FIGURE 30-1
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset