Visual Basic allows you to define generic classes, structures, interfaces, procedures, and delegates. The basic syntax is similar, so when you understand how to make generic classes, the others should be fairly easy.
To define a generic class, make a class declaration as usual. After the class name, add a parenthesis, the keyword Of, and a placeholder for a data type. For example, the following code shows the outline of a generic MostRecentList class. Its declaration takes one type that the class internally names ItemType. This is similar to a variable name that you would give to a subroutine. The class’s code can use the name ItemType to refer to the type associated with the instance of the generic class.
Public Class MostRecentList(Of ItemType) ... End Class
For example, suppose that you want to make a list that can act as a most recently used (MRU) file list. It should be able to hold at most four items. New items are added at the top of the list, and the others are bumped down one position with the last item being dropped if the list contains too many items. If you add an existing item to the list, it jumps to the top of the list.
The following code shows a generic MostRecentList class. The Of ItemType clause indicates that the class will take a single type that it internally names ItemType. The class stores its items in a private list named m_Items. It declares this list using the generic list class defined in the System .Collections.Generic namespace, and it indicates that this is a list that will hold ItemType objects. This refers to the ItemType parameter used in the generic MostRecentList class’s declaration. If the program makes a MostRecentList of strings, m_Items is a list of strings.
In this code, the Item property procedures simply let the main program get and set the values in the m_Items list. The MaxItems property lets the program determine the number of items that the list can hold. The property Set routine saves the new size and then resizes the m_Items list appropriately if necessary. The Count property returns the number of items currently in the list. The subroutine Add first removes the new item if it is already in the list. It then adds the new item at the top of the list and removes the last item if the list now contains too many items. The Remove and RemoveAt routines simply call the m_Items list’s Remove and RemoveAt methods.
Imports System.Collections.Generic ' A list of at most MaxItems items. Public Class MostRecentList(Of ItemType) ' The Item property. Private m_Items As New List(Of ItemType) Public Property Item(ByVal index As Integer) As ItemType Get Return m_Items(index) End Get Set(ByVal value As ItemType) m_Items(index) = value End Set End Property ' The MaxItems property. Private m_MaxItems As Integer = 4 Public Property MaxItems() As Integer Get Return m_MaxItems End Get Set(ByVal value As Integer) m_MaxItems = value ' Resize appropriately. Do While m_Items.Count > m_MaxItems m_Items.RemoveAt(m_Items.Count - 1) Loop End Set End Property ' The current number of items. Public ReadOnly Property Count() As Integer Get Return m_Items.Count End Get End Property ' Add an item to the top of the list. Public Sub Add(ByVal value As ItemType) ' Remove the item if it is present. If m_Items.Contains(value) Then m_Items.Remove(value) ' Add the item to the top of the list. m_Items.Insert(0, value) ' Make sure there are at most MaxItems items. If m_Items.Count > m_MaxItems Then m_Items.RemoveAt(m_Items.Count - 1) End Sub ' Remove an item. Public Sub Remove(ByVal value As ItemType) m_Items.Remove(value) End Sub ' Remove an item at a specific position. Public Sub RemoveAt(ByVal index As Integer) m_Items.RemoveAt(index) End Sub End Class
The following code creates a new MostRecentList of strings and then adds some values to it:
Dim the_items As New MostRecentList(Of String) the_items.Add("Apple") the_items.Add("Banana") the_items.Add("Cherry") the_items.Add("Date") the_items.Add("Banana") the_items.Add("Fig")
After this code executes, the list contains the values in the following order: Fig, Banana, Date, Cherry.
You can give constructors to a generic class just as you can give them to any other class. For example, the following constructor initializes the MostRecentList class’s MaxItem property:
' Initialize MaxItems for the new list. Public Sub New(ByVal max_items As Integer) MaxItems = max_items End Sub
To use the constructor, the main program adds normal parameters after the type parameters in the object declaration. The following statement creates a new MostRecentList of strings, passing its constructor the value 4:
Dim the_items As New MostRecentList(Of String)(4)
If you want the class to work with more than one type, you can add other types to the declaration separated by commas. For example, suppose that you want to create a list of data items associated with two keys. You might want to be able to look up customer data using a customer ID or customer name.
The following code defines the generic PairDictionary class. This class acts as a dictionary that associates a key value with a pair of data values. Notice how the Class declaration includes three data types named KeyType, DataType1, and DataType2.
The PairDictionary class defines its own private DataPair class to hold data pairs. The DataPair class has two public variables of types DataType1 and DataType2. Its only method is a constructor that makes initializing the two variables easier.
The PairDictionary class then declares a generic Dictionary object named m_Dictionary using the key type KeyType and data type DataPair. It delegates most of its work to the m_Dictionary object.
PairDictionary then provides Count, Add, Clear, ContainsKey, GetItem, SetItem, Keys, and Remove methods. Notice how it delegates these to the m_Dictionary object and how it uses the DataPair class to store values in m_Dictionary.
Imports System.Collections.Generic ' A Dictionary that associates ' a pair of data values with each key. Public Class PairDictionary(Of KeyType, DataType1, DataType2) ' A structure to hold paired data. Private Structure DataPair Public Data1 As DataType1 Public Data2 As DataType2 Public Sub New(ByVal data_value1 As DataType1, _ ByVal data_value2 As DataType2) Data1 = data_value1 Data2 = data_value2 End Sub End Structure ' A Dictionary to hold the paired data. Private m_Dictionary As New Dictionary(Of KeyType, DataPair) ' Return the number of data pairs. Public ReadOnly Property Count() As Integer Get Return m_Dictionary.Count End Get End Property ' Add a key and data pair. Public Sub Add(ByVal key As KeyType, ByVal _ data_value1 As DataType1, _ ByVal data_value2 As DataType2) m_Dictionary.Add(key, New DataPair(data_value1, data_value2)) End Sub ' Remove all data. Public Sub Clear() m_Dictionary.Clear() End Sub ' Return True if the PairDictionary contains this key. Public Function ContainsKey(ByVal key As KeyType) As Boolean Return m_Dictionary.ContainsKey(key) End Function ' Return a data pair. Public Sub GetItem(ByVal key As KeyType, _ ByRef data_value1 As DataType1, _ ByRef data_value2 As DataType2) Dim data_pair As DataPair = m_Dictionary.Item(key) data_value1 = data_pair.Data1 data_value2 = data_pair.Data2 End Sub ' Set a data pair. Public Sub SetItem(ByVal key As KeyType, _ ByVal data_value1 As DataType1, _ ByVal data_value2 As DataType2) m_Dictionary.Item(key) = _ New DataPair(data_value1, data_value2) End Sub ' Return a collection containing the keys. Public ReadOnly Property Keys() As System.Collections.ICollection Get Return m_Dictionary.Keys() End Get End Property ' Remove a particular entry. Public Sub Remove(ByVal key As KeyType) m_Dictionary.Remove(key) End Sub End Class
The following code creates an instance of the generic PairDictionary class that uses integers as keys and strings for both data values. It adds three entries to the PairDictionary and then retrieves and displays the entry with key value 32.
' Create the PairDictionary and add some data. Dim pair_dictionary As New PairDictionary(Of Integer, String, String) pair_dictionary.Add(10, "Ann", "Archer") pair_dictionary.Add(32, "Bill", "Beach") pair_dictionary.Add(17, "Cynthia", "Campos") ' Print the values for index 32. Dim value1 As String = "" Dim value2 As String = "" pair_dictionary.GetItem(32, value1, value2) Debug.WriteLine(value1 & ", " & value2)
To get the most out of your generic classes, you should make them as flexible as possible. Depending on what the class will do, however, you may need to constrain the types used to create instances of the generic.
For example, consider the generic MostRecentList class described earlier in this chapter. It stores at most a certain number of objects in a list. When you add an object to the list, the class first removes the object from the list if it is already present.
That works with simple data types such as integers and strings. However, suppose that you want the list to hold Employee objects. When you add a new Employee object, the list tries to remove the item if it is already present in its m_Items list. However, you are adding a new instance of the Employee class. The object may have the same values as an object that is already in the list, but the list won’t know that because the values are stored in two different objects.
What the list needs is a way to compare objects in the list to see if they are equal. It can then look through the list and remove an existing item if it matches the new one.
One way to allow the list to compare items is to guarantee that the items implement the IComparable interface. Then the program can use their CompareTo methods to see if two objects match.
The following code shows a new version of MostRecentList. Instead of calling the m_Items list’s Remove method directly, the Add method now calls the class’s Remove method. That method loops through the list using each item’s CompareTo method to see if the item matches the target item. If there is a match, the program removes the item from the list.
Public Class MostRecentList(Of ItemType As IComparable) ... ' Add an item to the top of the list. Public Sub Add(ByVal value As ItemType) ' Remove the item if it is present. Remove(value) ' Add the item to the top of the list. m_Items.Insert(0, value) ' Make sure there are at most MaxItems items. If m_Items.Count > m_MaxItems Then m_Items.RemoveAt(m_Items.Count - 1) End Sub ' Remove an item. Public Sub Remove(ByVal value As ItemType) ' Find the item. For i As Integer = m_Items.Count - 1 To 0 Step -1 If value.CompareTo(m_Items(i)) = 0 Then m_Items.RemoveAt(i) End If Next i End Sub ... End Sub
A type’s As clause can specify any number of interfaces and at most one class from which the type must be derived. It can also include the keyword New to indicate that the type used must provide a constructor that takes no parameters. If you include more than one constraint, the constraints should be separated by commas and enclosed in brackets.
The following code defines the StrangeGeneric class that takes three type parameters. The first type must implement the IComparable interface and must provide an empty constructor. The second type has no constraints, and the third type must be a class that inherits from Control.
Public Class StrangeGeneric(Of Type1 As {IComparable, New}, Type2, _ Type3 As Control) ... End Class
The following code declares an instance of the StrangeGeneric class:
Dim my_strange_generic As New StrangeGeneric(Of Integer, Employee, Button)
Constraining a type gives Visual Basic more information about that type, so it lets you use the properties and methods defined by the type. In the previous code, for example, if a variable is of type Type3, then Visual Basic knows that it inherits from the Control class, so you can use Control properties and methods such as Anchor, BackColor, Font, and so forth.