2.5. Build Typesafe Generic Classes
Programmers often face a difficult
choice. On one hand, it's keenly important to build
solutions that are as generic as possible, so that they can be reused
in different scenarios. For example, why build a
CustomerCollection class that accepts only objects
of type Customer when you can build a generic
Collection class that can be configured to accept
objects of any type? On the other hand, performance and type safety
considerations can make a generic solution less desirable. If you use
a generic .NET Collection class to store
Customer objects, for example, how can you be sure
that someone won't accidentally insert another type
of object into the collection, causing an insidious problem later on?
Note: Need to create a class that's flexible
enough to work with any type of object, but able to restrict the
objects it accepts in any given instance? With generics, VB has the
perfect solution.
Visual Basic 2005 and .NET 2.0 provide a solution called
generics. Generics are classes that are
parameterized by type. In other words, generics
allow you to create a class template that supports any type. When you
instantiate that class, you specify the type you want to use, and
from that point on, your object is "locked
in" to the type you chose.
2.5.1. How do I do that?
An example of where the use of generics makes great sense is the
System.Collections.ArrayList class.
ArrayList is an
all-purpose, dynamically self-sizing collection. It can hold ordinary
.NET objects or your own custom objects. In order to support this,
ArrayList treats everything as the base
Object type.The problem is that there's no way to impose any
restrictions on how ArrayList works. For example,
if you want to use ArrayList to store a collection
of Customer objects, you have no way to be sure
that a faulty piece of code won't accidentally
insert strings, integers, or some other type of object, causing
future headaches. For this reason, developers often create their own
strongly typed collection classesin fact, the .NET class
library is filled with dozens of them.Generics can solve this problem. For example, using generics you can
declare a class that works with any type using the
Of keyword:
Public Class GenericList(Of ItemType)In this case, you are creating a new class named
' (Code goes here)
End Class
GenericList that can work with any type of object.
However, the client needs to specify what type should be used. In
your class code, you refer to that type as
ItemType. Of course, ItemType
isn't really a typeit's just
a placeholder for the type that you'll choose when
you instantiate a GenericList object.Example 2-2 shows the complete code for a simple
typesafe ArrayList.
Example 2-2. A typesafe collection using generics
Public Class GenericList(Of ItemType)The GenericList class wraps an ordinary
Inherits CollectionBase
Public Function Add(ByVal value As ItemType) As Integer
Return List.Add(value)
End Function
Public Sub Remove(ByVal value As ItemType)
List.Remove(value)
End Sub
Public ReadOnly Property Item(ByVal index As Integer) As ItemType
Get
' The appropriate item is retrieved from the List object and
' explicitly cast to the appropriate type, and then returned.
Return CType(List.Item(index), ItemType)
End Get
End Property
End Class
ArrayList, which is provided through the
List property of the
CollectionBase class it inherits from. However,
the GenericList class works differently than an
ArrayList by providing strongly typed
Add( ) and Remove( ) methods,
which use the ItemType placeholder.Here's an example of how you might use the
GenericList class to create an
ArrayList collection that only supports strings:
' Create the GenericList instance, and choose a type (in this case, string).There's no limit to how many ways you can
Dim List As New GenericList(Of String)
' Add two strings.
List.Add("blue")
List.Add("green")
' The next statement will fail because it has the wrong type.
' There is no automatic way to convert a GUID to a string.
' In fact, this line won't ever run, because the compiler
' notices the problem and refuses to build the application.
List.Add(Guid.NewGuid( ))
parameterize a class. In the GenericList example,
there's only one type parameter. However, you could
easily create a class that works with two or three types of objects,
and allows you to make all of these types generic. To use this
approach, just separate each parameter type with a comma (between the
brackets at the beginning of a class).For example, consider the following
GenericHashTable class, which allows you to define
the type of the items the collection will store
(ItemType), as well as the type of the keys you
will use to index those items (KeyType):
Public Class GenericHashTable(Of ItemType, KeyType)Another important feature in generics is the ability to apply
Inherits DictionaryBase
' (Code goes here.)
End Class
constraints to parameters. Constraints restrict
the types allowed for a given generic class. For example, suppose you
want to create a class that supports only types that implement a
particular interface. To do so, first declare the type or types the
class accepts and then use the As keyword to
specify the base class that the type must derive from, or the
interface that the type must implement.Here's an example that restricts the items stored in
a GenericList to serializable items. This feature
would be useful if, for example, you wanted to add a method to the
GenericList that required serialization, such as a
method that writes all the items in the list to a stream:
Public Class SerializableList(Of ItemType As ISerializable)Similarly, here's a collection that can contain any
Inherits CollectionBase
' (Code goes here.)
End Class
type of object, provided it's derived from the
System.Windows.Forms.Control class. The end result
is a collection that's limited to controls, like the
one exposed by the Forms.Controls property on a
window:
Public Class ControlCollection(Of ItemType As Control)Sometimes, your generic class might need the ability to create the
Inherits CollectionBase
' (Code goes here.)
End Class
parameter class. For example, the GenericList
example might need the ability to create an instance of the item you
want to store in the collection. In this case, you need to use the
New constraint. The New
constraint allows only parameter types that have a public
zero-argument constructor, and aren't marked
MustInherit. This ensures that your code can
create instances of the parameter type. Here's a
collection that imposes the New constraint:
Public Class GenericList(Of ItemType As New)It's also worth noting that you can define as many
Inherits CollectionBase
' (Code goes here.)
End Class
constraints as you want, as long as you group the list of constraints
in curly braces, as
shown here:
Public Class GenericList(Of ItemType As {ISerializable, New})Constraints are enforced by the compiler, so if you violate a
Inherits CollectionBase
' (Code goes here.)
End Class
constraint rule when using a generic class, you
won't be able to compile your application.
Note: Generics are built into the Common Language Runtime. That
means they are supported in all first-class . NET languages,
including C#.
2.5.2. What about...
...using generics with other code structures? Generics
don't just work with classes.
They can also be used in structures,
interfaces, delegates, and even methods. For more information, look
for the index entry "generics" in
the MSDN Help. For more in-depth examples of advanced generic
techniques, you can refer to a Microsoft whitepaper at http://www.msdn.net/library/en-us/dnvs05/html/vb2005_generics.asp.Incidentally, the .NET Framework designers are well aware of the
usefulness of generic collections, and they've
already created several for you to use out of the box.
You'll find them in the new
Systems.Collections.Generic
namespace. They include:List (a basic collection like the
GenericList example)Dictionary (a name-value collection that indexes
each item with a key)LinkedList (a linked list, where each item points
to the next item in the chain)Queue (a first-in-first-out collection)Stack (a last-in-first-out collection)SortedList (a name-value collection
that's kept in perpetually sorted order)
Most of these types duplicate one of the types in the
System.Collections namespace. The old collections
remain for backward compatibility.