Generics

Generics refer to the technology built into the .NET Framework (introduced originally with the .NET Framework version 2.0) that enables you to define a template and then declare variables using that template. The template defines the operations that the new type can perform; and when you declare a variable based on the template, you are creating a new type. The benefit of generics over untyped collections or arrays is that a generic template makes it easier for collection types to be strongly typed. The introduction of covariance in .NET Framework 4 makes it easier to reuse the template code in different scenarios.

The primary motivation for adding generics to .NET was to enable the creation of strongly typed collection types. Because generic collection types are strongly typed, they are significantly faster than the previous inheritance-based collection model. Anywhere you presently use collection classes in your code, you should consider revising that code to use generic collection types instead.

Visual Basic 2012 allows not only the use of preexisting generics, but also the creation of your own generic templates. Because the technology to support generics was created primarily to build collection classes, it naturally follows that you might create a generic collection anytime you would otherwise build a normal collection class. More specifically, anytime you find yourself using the Object data type, you should instead consider using generics.

Using Generics

There are many examples of generic templates in the .NET Base Class Library (BCL). Many of them can be found in the System.Collections.Generic namespace, but others are scattered through the BCL as appropriate. Many of the examples focus on generic collection types, but this is only because it is here that the performance gains, due to generics, are seen. In most cases, generics are used less for performance gains than for the strong typing benefits they provide. As noted earlier, anytime you use a collection data type, you should consider using the generic equivalent instead.

A generic is often written as something like List(Of T). The type (or class) name in this case is List. The letter T is a placeholder, much like a parameter. It indicates where you must provide a specific type value to customize the generic. For instance, you might declare a variable using the List(Of T) generic:

Dim data As New List(Of Date)

In this case, you are specifying that the type parameter, T, is a Date. By providing this type, you are specifying that the list will contain only values of type Date. To make this clearer, let's contrast the new List(Of T) collection with the older ArrayList type.

When you work with an ArrayList, you are working with a type of collection that can store many types of values at the same time:

Dim data As New ArrayList()
data.Add("Hello")
data.Add(5)
data.Add(New Customer())

This ArrayList is loosely typed, internally always storing the values as type Object. This is very flexible but relatively slow because it is late bound. What this means is that when you determine something at runtime you are binding to that type. Of course, it offers the advantage of being able to store any data type, with the disadvantage that you have no control over what is actually stored in the collection.

The List(Of T) generic collection is quite different. It is not a type at all; it is just a template. A type is not created until you declare a variable using the template:

Dim data As New Generic.List(Of Integer)
data.Add(5)
data.Add(New Customer()) ' throws an exception
data.Add("Hello") ' throws an exception

When you declare a variable using the generic, you must provide the type of value that the new collection will hold. The result is that a new type is created—in this case, a collection that can hold only Integer values.

The important thing here is that this new collection type is strongly typed for Integer values. Not only does its external interface (its Item and Add methods, for instance) require Integer values, but its internal storage mechanism works only with type Integer. This means that it is not late bound like ArrayList, but rather is early bound. The net result is much higher performance, along with all the type-safety benefits of being strongly typed.

Generics are useful because they typically offer better performance than traditional collection classes. In some cases, they can also save you from writing code, as generic templates can provide code reuse, whereas traditional classes cannot. Finally, generics can sometimes provide better type safety compared to traditional classes, as a generic adapts to the specific type you require, whereas classes often must resort to working with a more general type such as Object.

Generics come in two forms: generic types and generic methods. For instance, List(Of T) is a generic type in that it is a template that defines a complete type or class. In contrast, some otherwise normal classes have single methods that are just method templates and that assume a specific type when they are called. You will look at both scenarios.

Nullable Types

In addition to having the option to explicitly check for the DBNull value, Visual Basic 2005 introduced the capability to create a nullable value type. In the background, when this syntax is used, the system creates a reference type containing the same data that would be used by the value type. Your code can then check the value of the nullable type before attempting to set this into a value type variable. Nullable types are built using generics. Note that while the Visual Basic keyword for null is Nothing, it is common to discuss this type as supporting a null value even in Visual Basic.

For consistency let's take a look at how nullable types work. The key, of course, is that value types can't be set to null (aka Nothing). This is why nullable types aren't value types. The following statements show how to declare a nullable integer:

Dim intValue as Nullable(Of Integer)
Dim intValue2 as Integer?

Both intValue and intValue2 act like integer variables, but they aren't actually of type Integer. As noted, the syntax is based on generics, but essentially you have just declared an object of type Nullable and declared that this object will, in fact, hold integer data. Thus, both of the following assignment statements are valid:

intValue = 123
intValue = Nothing

However, at some point you are going to need to pass intValue to a method as a parameter, or set some property on an object that is looking for an object of type Integer. Because intValue is actually of type Nullable, it has the properties of a nullable object. The Nullable class has two properties of interest when you want to get the underlying value. The first is the property value. This represents the underlying value type associated with this object. In an ideal scenario, you would just use the value property of the Nullable object in order to assign to your actual value a type of integer and everything would work. If the intValue.value wasn't assigned, you would get the same value as if you had just declared a new Integer without assigning it a value which would be 0.

Unfortunately, that's not how the nullable type works. If the intValue.value property contains Nothing and you attempt to assign it, then it throws an exception. To avoid getting this exception, you always need to check the other property of the nullable type: HasValue. The HasValue property is a Boolean that indicates whether a value exists; if one does not, then you shouldn't reference the underlying value. The following code example shows how to safely use a nullable type:

Dim intValue as Nullable(Of Integer)
Dim intI as Integer
If intValue.HasValue Then
    intI = intValue.Value
End If

Of course, you could add an Else statement to the preceding and use either Integer.MinValue or Integer.MaxValue as an indicator that the original value was Nothing. The key point here is that nullable types enable you to easily work with nullable columns in your database, but you must still verify whether an actual value or null was returned.

Generic Types

Now that you have a basic understanding of generics and how they compare to regular types, let's get into some more detail. To do this, you will make use of some other generic types provided in the .NET Framework. A generic type is a template that defines a complete class, structure, or interface. When you want to use such a generic, you declare a variable using the generic type, providing the real type (or types) to be used in creating the actual type of your variable.

Basic Usage

First, consider the Dictionary(Of K, T) generic. This is much like the List(Of T) discussed earlier, but this generic requires that you define the types of both the key data and the values to be stored. When you declare a variable as Dictionary(Of K, T), the new Dictionary type that is created accepts only keys of the one type and values of the other.

Add the following Sample Dictionary method to the Form1.vb file and call it from the ButtonTest_Click event handler:

Private Sub SampleDict()
    Dim data As New Generic.Dictionary(Of Integer, String)
    data.Add(5, "Bill")
    data.Add(1, "Johnathan")
    For Each item As KeyValuePair(Of Integer, String) In data
        TextBoxOutput.AppendText("Data: " & item.Key & ", " &
              item.Value)
        TextBoxOutput.AppendText(Environment.NewLine)
    Next
    TextBoxOutput.AppendText(Environment.NewLine)
       
End Sub

As you type, watch the IntelliSense information on the Add method. Notice how the key and value parameters are strongly typed based on the specific types provided in the declaration of the data variable. In the same code, you can create another type of Dictionary as follows:

Private Sub SampleDict()
    Dim data As New Generic.Dictionary(Of Integer, String)
    Dim info As New Generic.Dictionary(Of Guid, Date)
    data.Add(5, "Bill")
    data.Add(1, "Johnathan")
    For Each item As KeyValuePair(Of Integer, String) In data
        TextBoxOutput.AppendText("Data: " & item.Key & ", " &
              item.Value)
        TextBoxOutput.AppendText(Environment.NewLine)
    Next
    TextBoxOutput.AppendText(Environment.NewLine)
    info.Add(Guid.NewGuid, Now)
    For Each item As KeyValuePair(Of Guid, Date) In info
        TextBoxOutput.AppendText("Info: " & item.Key.ToString &
           ", " & item.Value)
        TextBoxOutput.AppendText(Environment.NewLine)
    Next
    TextBoxOutput.AppendText(Environment.NewLine)
       
End Sub

This code contains two completely different types. Both have the behaviors of a Dictionary, but they are not interchangeable because they have been created as different types.

Generic types may also be used as parameters and return types. For instance, adding the following Function LoadData method implements a generic return type:

   Private Function LoadData() As Generic.Dictionary(Of Integer, String)
     Dim data As New Generic.Dictionary(Of Integer, String)
     data.Add(5, "William")
     data.Add(1, "Johnathan")
     Return data
   End Function

Next, to call this method from the ButtonTest_Click event handler, add the following to the bottom of the SampleDict method:

     Dim results As Generic.Dictionary(Of Integer, String)
     results = LoadData()
     For Each item As KeyValuePair(Of Integer, String) In results
         TextBoxOutput.AppendText("Results: " & item.Key & ", " &
               item.Value)
         TextBoxOutput.AppendText(Environment.NewLine)
     Next
     TextBoxOutput.AppendText(Environment.NewLine)
        

The results of this method are displayed in Figure 7.3. Note that you will run this at a different time and will generate a new Globally Unique Identifier; thus your results will not exactly match what is shown.

Figure 7.3 Display of output when running sample code

7.3

This works because both the return type of the function and the type of the data variable are exactly the same. Not only are they both Generic.Dictionary derivatives, they have exactly the same types in the declaration.

The same is true for parameters:

   Private Sub DoWork(ByVal values As Generic.Dictionary(Of Integer, String))
     ' do work here
   End Sub

Again, the parameter type is defined not only by the generic type, but also by the specific type values used to initialize the generic template.

Inheritance

It is possible to inherit from a generic type as you define a new class. For instance, the .NET BCL defines the System.ComponentModel.BindingList(Of T) generic type. This type is used to create collections that can support data binding. You can use this as a base class to create your own strongly typed, data-bindable collection. Add new classes named Customer and CustomerList with the following:

  • Class Customer (Customer.vb)
Public Class Customer
  Public Property Name() As String
 End Class
  • Class Customer List (CustomerList.vb)
Public Class CustomerList
  Inherits System.ComponentModel.BindingList(Of Customer)

  Private Sub CustomerList_AddingNew(ByVal sender As Object,
    ByVal e As System.ComponentModel.AddingNewEventArgs) Handles Me.AddingNew
    Dim cust As New Customer()
    cust.Name = "<new>"
    e.NewObject = cust
  End Sub
End Class

When you inherit from BindingList(Of T), you must provide a specific type—in this case, Customer. This means that your new CustomerList class extends and can customize BindingList(Of Customer). Here you are providing a default value for the Name property of any new Customer object added to the collection.

When you inherit from a generic type, you can employ all the normal concepts of inheritance, including overloading and overriding methods, extending the class by adding new methods, handling events, and so forth.

To see this in action, add a new Button control named ButtonCustomer to Form1 and add a new form named FormCustomerGrid to the project. Add a DataGridView control to FormCustomerGrid and dock it by setting the Dock property to the Fill in the parent container option.

Next, double-click on the new button on Form1 to generate a click event handler for the button. In the code-behind ButtonCustomer_Click event handler, add the following:

     FormCustomerGrid.ShowDialog()

Next, customize the Form Customer Grid by adding the following behind FormCustomerGrid:

Public Class FormCustomerGrid
        Dim list As New CustomerList()
        Private Sub FormCustomerGrid_Load(ByVal sender As System.Object,
                            ByVal e As System.EventArgs) Handles MyBase.Load
              DataGridView1.DataSource = list
 End Sub
End Class

This code creates an instance of CustomerList and data-binds the list as the DataSource for the DataGridView control. When you run the program and click the button to open the CustomerForm, notice that the grid contains a newly added Customer object. As you interact with the grid, new Customer objects are automatically added, with a default name of <new>. An example is shown in Figure 7.4.

Figure 7.4 Using a DataGridView control in a Windows Form

7.4

All this functionality of adding new objects and setting the default Name value occurs because CustomerList inherits from BindingList(Of Customer).

Generic Methods

A generic method is a single method that is called not only with conventional parameters, but also with type information that defines the method. Generic methods are far less common than generic types. Due to the extra syntax required to call a generic method, they are also less readable than a normal method.

A generic method may exist in any class or module; it does not need to be contained within a generic type. The primary benefit of a generic method is avoiding the use of CType or DirectCast to convert parameters or return values between different types.

It is important to realize that the type conversion still occurs. Generics merely provide an alternative mechanism to use instead of CType or DirectCast.

Without generics, code often uses the Object type, such as:

   Public Function AreEqual(ByVal a As Object, ByVal b As Object) As Boolean
     Return a.Equals(b)
   End Function

The problem with this Object type code such as this is that a and b could be anything. There is no restriction here—nothing to ensure that they are even the same type. An alternative is to use generics, such as:

   Public Function AreEqual(Of T)(ByVal a As T, ByVal b As T) As Boolean
     Return a.Equals(b)
   End Function

Now a and b are forced to be the same type, and that type is specified when the method is invoked. In order to test these, create a new Sub method using the following:

 Private Sub CheckEqual()
     Dim result As Boolean
     ' use normal method
     result = AreEqual(1, 2)
     result = AreEqual("one", "two")
     result = AreEqual(1, "two")
     ' use generic method
     result = AreEqual(Of Integer)(1, 2)
     result = AreEqual(Of String)("one", "two")
     'result = AreEqual(Of Integer)(1, "two")
 End Sub

However, why not just declare the method as a Boolean? This code will probably cause some confusion. The first three method calls are invoking the normal AreEqual method. Notice that there is no problem asking the method to compare an Integer and a String.

The second set of calls looks very odd. At first glance, they look like nonsense to many people. This is because invoking a generic method means providing two sets of parameters to the method, rather than the normal one set of parameters.

The first set of parameters contain the type or types required to define the method. This is much like the list of types you must provide when declaring a variable using a generic class. In this case, you're specifying that the AreEqual method will be operating on parameters of type Integer.

The second set of parameters contains the conventional parameters that you'd normally supply to a method. What is special in this case is that the types of the parameters are being defined by the first set of parameters. In other words, in the first call, the type is specified to be Integer, so 1 and 2 are valid parameters. In the second call, the type is String, so “one” and “two” are valid. Notice that the third line is commented out. This is because 1 and “two” aren't the same type; with Option Strict On, the compiler will flag this as an error. With Option Strict Off, the runtime will attempt to convert the string at runtime and fail, so this code will not function correctly.

..................Content has been hidden....................

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