Generic Types

Recall that a generic type is a class, structure, or interface template. You can create such templates yourself to provide better performance, strong typing, and code reuse to the consumers of your types.

Classes

A generic class template is created in the same way that you create a normal class, except that you require the consumer of your class to provide you with one or more types for use in your code. In other words, as the author of a generic template, you have access to the type parameters provided by the user of your generic.

For example, add a new class named SingleLinkedList to the project:

Public Class SingleLinkedList(Of T) End Class

In the declaration of the type, you specify the type parameters that will be required:

Public Class SingleLinkedList(Of T)

In this case, you are requiring just one type parameter. The name, T, can be any valid variable name. In other words, you could declare the type like this:

Public Class SingleLinkedList(Of ValueType)

Make this change to the code in your project.

By convention (carried over from C++ templates), the variable names for type parameters are single uppercase letters. This is somewhat cryptic, and you may want to use a more descriptive convention for variable naming.

Whether you use the cryptic standard convention or more readable parameter names, the parameter is defined on the class definition. Within the class itself, you then use the type parameter anywhere that you would normally use a type (such as String or Integer).

To create a linked list, you need to define a Node class. This will be a nested class:

Public Class SingleLinkedList(Of ValueType) #Region " Node class " Private Class Node

Private mValue As ValueType

Public ReadOnly Property Value() As ValueType Get

Return mValue End Get End Property

Public Property NextNode() As Node

Public Sub New(ByVal value As ValueType, ByVal newNode As Node) mValue = value NextNode = newNode End Sub End Class #End Region End Class

Code snippet from SingleLinkedList

Notice how the mValue variable is declared as ValueType. This means that the actual type of mValue depends on the type supplied when an instance of SingleLinkedList is created.

Because ValueType is a type parameter on the class, you can use ValueType as a type anywhere in the code. As you write the class, you cannot tell what type ValueType will be. That information is provided by the user of your generic class. Later, when someone declares a variable using your generic type, that person will specify the type, like this:

Dim list As New SingleLinkedList(Of Double)

At this point, a specific instance of your generic class is created, and all cases of ValueType within your code are replaced by the Visual Basic compiler with Double. Essentially, this means that for this specific instance of SingleLinkedList, the mValue declaration ends up as follows:

Private mValue As Double

Of course, you never get to see this code, as it is dynamically generated by the .NET Framework's JIT compiler at runtime based on your generic template code.

The same is true for methods within the template. Your example contains a constructor method, which accepts a parameter of type ValueType. Remember that ValueType will be replaced by a specific type when a variable is declared using your generic.

So, what type is ValueType when you are writing the template itself? Because it can conceivably be any type when the template is used, ValueType is treated like the Object type as you create the generic template. This severely restricts what you can do with variables or parameters of ValueType within your generic code.

The mValue variable is of ValueType, which means it is basically of type Object for the purposes of your template code. Therefore, you can do assignments (as you do in the constructor code), and you can call any methods that are on the System.Object type:

> GetHashCode()

No operations beyond these basics are available by default. Later in the chapter, you will learn about the concept of constraints, which enables you to restrict the types that can be specified for a type parameter. Constraints have the added benefit that they expand the operations you can perform on variables or parameters defined based on the type parameter.

However, this capability is enough to complete the SingleLinkedList class. Add the following code to the class after the End Class from the Node class:

Private mHead As Node

Default Public Readonly Property Item(ByVal index As Integer) As ValueType Get

Dim current As Node = mHead For index = 1 To index current = current.NextNode If current Is Nothing Then

Throw New Exception("Item not found in list") End If Next

Return current.Value End Get End Property

Public Sub Add(ByVal value As ValueType)

mHead = New Node(value, mHead) End Sub

Public Sub Remove(ByVal value As ValueType) Dim current As Node = mHead Dim previous As Node = Nothing While current IsNot Nothing

If current.Value.Equals(value) Then If previous Is Nothing Then

' this was the head of the list mHead = current.NextNode Else previous.NextNode = current.NextNode End If Exit Sub End If previous = current current = current.NextNode End While

'got to the end without finding the item. Throw New Exception("Item not found in list") End Sub

Public Readonly Property Count() As Integer Get

Dim result As Integer = 0 Dim current As Node = mHead While current IsNot Nothing result += 1

current = current.NextNode End While Return result End Get End Property

Code snippet from SingleLinkedList

Notice that the Item property and the Add and Remove methods all use ValueType as either return types or parameter types. More important, note the use of the Equals method in the Remove method:

If current.Value.Equals(value) Then

The reason why this compiles is because Equals is defined on System.object and is therefore universally available. This code could not use the = operator because that is not universally available.

To try out the SingleLinkedList class, add the following method, which can be called from the ButtonTest Click method:

Private Sub CustomList()

Dim list As New SingleLinkedList(Of String)

list.Add("Nikita")

list.Add("Elena")

list.Add("Benajmin")

list.Add("William")

list.Add("Abigail")

list.Add("Johnathan")

TextBoxOutput.Clear()

TextBoxOutput.AppendText("Count: " & list.Count) TextBoxOutput.AppendText(Environment.NewLine) For index As Integer = 0 To list.Count - 1

TextBoxOutput.AppendText("Item: " & list.Item(index)) TextBoxOutput.AppendText(Environment.NewLine)

Next End Sub

Code snippet from Form1

When you run the code, you will see a display similar to Figure 8-5.

FiGURE 8-5

other Generic Class Features

Earlier in the chapter, you used the Dictionary generic, which specifies multiple type parameters. To declare a class with multiple type parameters, you use syntax like the following:

Public Class MyCoolType(Of T, V) Private mValue As T Private mData As V

Public Sub New(ByVal value As T, ByVal data As V) mValue = value mData = data End Sub End Class

In addition, it is possible to use regular types in combination with type parameters, as shown here:

Public Class MyCoolType(Of T, V) Private mValue As T Private mData As V Private mActual As Double

Public Sub New(ByVal value As T, ByVal data As V, ByVal actual As Double) mValue = value mData = data mActual = actual End Sub End Class

Other than the fact that variables or parameters of types T or V must be treated as type System.Object, you can write virtually any code you choose. The code in a generic class is really no different from the code you'd write in a normal class.

This includes all the object-oriented capabilities of classes, including inheritance, overloading, overriding, events, methods, properties, and so forth. However, there are some limitations on overloading. In particular, when overloading methods with a type parameter, the compiler does not know what that specific type might be at runtime. Thus, you can only overload methods in ways in which the type parameter (which could be any type) does not lead to ambiguity.

For instance, adding these two methods to MyCoolType before the .NET Framework 3.5 would have resulted in a compiler error:

Public Sub DoWork(ByVal data As Integer)

' do work here End Sub

Public Sub DoWork(ByVal data As V)

' do work here End Sub

Now this is possible due to the support for implicitly typed variables. During compilation in .NET, the compiler figures out what the data type of V should be. Next it replaces V with that type which allows your code to compile correctly. This was not the case prior to .NET 3.5. Before this version of the .NET Framework, this kind of code would have resulted in a compiler error. It wasn't legal because the compiler didn't know whether V would be an Integer at runtime. If V were to end up defined as an Integer, then you'd have two identical method signatures in the same class.

Classes and Inheritance

Not only can you create basic generic class templates, you can also combine the concept with inheritance. This can be as basic as having a generic template inherit from an existing class:

Public Class MyControls(Of T)

Inherits Control End Class

In this case, the MyControls generic class inherits from the Windows Forms Control class, thus gaining all the behaviors and interface elements of a Control.

Alternately, a conventional class can inherit from a generic template. Suppose that you have a simple generic template:

Public Class GenericBase(Of T) End Class

It is quite practical to inherit from this generic class as you create other classes:

Public Class Subclass

Inherits GenericBase(Of Integer) End Class

Notice how the Inherits statement not only references GenericBase, but also provides a specific type for the type parameter of the generic type. Anytime you use a generic type, you must provide values for the type parameters, and this is no exception. This means that your new Subclass actually inherits from a specific instance of GenericBase, where T is of type Integer.

Finally, you can also have generic classes inherit from other generic classes. For instance, you can create a generic class that inherits from the GenericBase class:

Public Class GenericSubclass(Of T) Inherits GenericBase(Of Integer) End Class

As with the previous example, this new class inherits from an instance of GenericBase, where T is of type Integer.

Things can get far more interesting. It turns out that you can use type parameters to specify the types for other type parameters. For instance, you could alter GenericSubclass like this:

Public Class GenericSubclass(Of V)

Inherits GenericBase(Of V) End Class

Notice that you're specifying that the type parameter for GenericBase is V — which is the type provided by the caller when declaring a variable of type GenericSubclass. Therefore, if a caller uses a declaration that creates an object as a GenericSubclass(Of String) then V is of type String. This means that the GenericSubclass is now inheriting from an instance of GenericBase, where its T parameter is also of type String. The point being that the type flows through from the subclass into the base class. If that is not complex enough, for those who just want a feel for how twisted this logic can become, consider the following class definition:

Public Class GenericSubclass(Of V)

Inherits GenericBase(Of GenericSubclass(Of V)) End Class

In this case, the GenericSubclass is inheriting from GenericBase, where the T type in GenericBase is actually based on the declared instance of the GenericSubclass type. A caller can create such an instance with the simple declaration which follows:

Dim obj As GenericSubclass(Of Date)

In this case, the GenericSubclass type has a V of type Date. It also inherits from GenericBase, which has a T of type GenericSubclass(Of Date).

Such complex relationships are typically not useful, in fact they are often counter productive making code difficult to follow and debug. The point was that it is important to recognize how types flow through generic templates, especially when inheritance is involved.

structures

You can also define generic Structure types. The basic rules and concepts are the same as for defining generic classes, as shown here:

Public Structure MyCoolStructure(Of T)

Public Value As T End Structure

As with generic classes, the type parameter or parameters represent real types that are provided by the user of the Structure in actual code. Thus, anywhere you see a T in the structure, it will be replaced by a real type such as String or Integer.

Code can use the Structure in a manner similar to how a generic class is used:

Dim data As MyCoolStructure(Of Guid)

When the variable is declared, an instance of the Structure is created based on the type parameter provided. In this example, an instance of MyCoolStructure that holds Guid objects has been created.

Interfaces

Finally, you can define generic interface types. Generic interfaces are a bit different from generic classes or structures because they are implemented by other types when they are used. You can create a generic interface using the same syntax used for classes and structures:

Public Interface ICoolInterface(Of T) Sub DoWork(ByVal data As T) Function GetAnswer() As T End Interface

Then the interface can be used within another type. For instance, you might implement the interface in a class:

Public Class ARegularClass

Implements ICoolInterface(Of String) Public Sub DoWork(ByVal data As String) _

Implements ICoolInterface(Of String).DoWork End Sub

Public Function GetAnswer() As String _

Implements ICoolInterface(Of String).GetAnswer End Function End Class

Notice that you provide a real type for the type parameter in the Implements statement and Implements clauses on each method. In each case, you are specifying a specific instance of the ICoolInterface interface — one that deals with the String data type.

As with classes and structures, an interface can be declared with multiple type parameters. Those type parameter values can be used in place of any normal type (such as String or Date) in any Sub, Function, Property, or Event declaration.

0 0

Post a comment