11.1. Generics

For anyone unfamiliar with templates in C++ or the concept of a generic type, this section begins with a simple example that illustrates where a generic can replace a significant amount of coding, while also maintaining strongly typed code. This example stores and retrieves integers from a collection. As you can see from the following code snippet, there are two ways to do this: either using a non-typed ArrayList, which can contain any type, or using a custom-written collection:

'Option 1 - Non-typed Arraylist
'Creation - unable to see what types this list contain
Dim nonTypedList As New ArrayList
'Adding - no type checking, so can add any type
nonTypedList.Add(1)
nonTypedList.Add("Hello")
nonTypedList.Add(5.334)
'Retrieving - no type checking, must cast (should do type checking too)
Dim output As Integer = CInt(nonTypedList.Item(1))

'Option 2 - Strongly typed custom written collection
'Creation - custom collection
Dim myList As New IntegerCollection
'Adding - type checking, so can only add integers
myList.Add(1)
'Retrieving - type checking, so no casting required
output = myList.Item(0)

Clearly, the second approach is preferable because it ensures that you put only integers into the collection. However, the downside of this approach is that you have to create collection classes for each type you want to put in a collection. You can rewrite this example using the generic List class:

'Creation - generic list, specifying the type of objects it contains
Dim genericList As New List(Of Integer)
'Adding - type checking
genericList.Add(1)
'Retrieving - type checking
output = genericList.Item(0)

This example has the benefits of the strongly typed collection without the overhead of having to rewrite the collection for each type. To create a collection that holds strings, all you have to do is change the Type argument of the List—for example, List(Of String).

In summary, generic types have one or more Type parameters that will be defined when an instance of the type is declared. From the example you just saw, the class List has a Type parameter, T, which, when specified, determines the type of items in the collection. The following sections describe in more detail how to consume, create, and constrain generic types.

11.1.1. Consumption

You have just seen a VB.NET example of how to consume the generic List to provide either a collection of integers or a collection of strings. You can accomplish this by supplying the Type parameter as part of the declaration. The following code snippets illustrate the consumption of generic types for both VB.NET and C#:

C#

Dictionary<String,double> scores = new Dictionary<String,double>();

VB.NET

Dim scores As New Dictionary(Of String, Double)

There are also generic methods, which also have a Type parameter that must be supplied when the method is invoked. This is illustrated in calling the Choose method, which randomly picks one of the two arguments passed in:

C#

newValue=Chooser.Choose<int>(5, 6);
newValue=Chooser.Choose(7, 8);

VB.NET

newValue = Chooser.Choose(of Integer)(5,6)
newValue = Chooser.Choose(7,8)

In these examples, you can see that a Type argument has been supplied in the first line but omitted in the second line. You're able to do this because type inferencing kicks in to automatically determine what the Type argument should be.

11.1.2. Creation

To create a generic type, you need to define the Type parameters that must be provided when the type is constructed, performed as part of the type signature. In the following example, the ObjectMapper class defines two Type parameters, TSource and TDestination, that need to be supplied when an instance of this class is declared:

C#

public class ObjectMapper<TSource, TDestination>
{
    private TSource source;
    private TDestination destination;

    public ObjectMapper(TSource src , TDestination dest )
    {
        source = src;
        destination = dest;
    }
}

VB.NET

Public Class ObjectMapper(Of TSource, TDestination)
    Private source As TSource
    Private destination As TDestination

    Public Sub New(ByVal src As TSource, ByVal dest As TDestination)
        source = src
        destination = dest
    End Sub
End Class

A naming convention for Type parameters is to begin them with the letter T, followed by some sort of descriptive name if there is more than one Type parameter. In this case, the two parameters define the type of Source and Destination objects to be provided in the mapping.

Generic methods are defined using a similar syntax as part of the method signature. Although generic methods may often be placed within a generic type, that is not a requirement; in fact, they can exist anywhere a non-generic method can be written. The following CreateObjectMapper method takes two objects of different types and returns a new ObjectMapper object, passing the Type arguments for the method through to the constructor:

C#

public static ObjectMapper<TCreateSrc, TCreateDest>
    CreateObjectMapper<TCreateSrc, TCreateDest>
                                                 (TCreateSrc src, TCreateDest dest)
{
    return new ObjectMapper<TCreateSrc, TCreateDest>(src, dest);
}

VB.NET

Public Shared Function CreateObjectMapper(Of TCreateSrc, TCreateDest) _
                            (ByVal src As TCreateSrc, ByVal dest As TCreateDest) _
                                       As ObjectMapper(Of TCreateSrc, TCreateDest)
    Return New ObjectMapper(Of TCreateSrc, TCreateDest)(src, dest)
End Function

11.1.3. Constraints

So far, you have seen how to create and consume generic types and methods. However, having Type parameters limits what you can do with the parameter because you only have access to the basic object methods such as GetType, Equals, and ToString. Without more information about the Type parameter, you are limited to building simple lists and collections. To make generics more useful, you can place constraints on the Type parameters to ensure that they have a basic set of functionality. The following example places constraints on both parameters:

C#

public class ObjectMapper<TSource, TDestination>
                                  : IComparable<ObjectMapper<TSource,TDestination>>
                                 where TSource: IComparable<TSource>
                                 where TDestination: new()
{
    private TSource source;
    private TDestination destination;

    public ObjectMapper(TSource src)
    {
        source = src;
        destination = new TDestination();
    }
       public int
   CompareTo(ObjectMapper<TSource,TDestination> mapper)
    {
        return source.CompareTo(mapper.source);
    }
}

VB.NET

Public Class ObjectMapper(Of TSource As IComparable(Of TSource), _
                          TDestination As New)
    Implements IComparable(Of ObjectMapper(Of TSource, TDestination))

    Private source As TSource
    Private destination As TDestination

    Public Sub New(ByVal src As TSource)
        source = src
        destination = new TDestination
    End Sub
    Public Function CompareTo _
               (ByVal other As ObjectMapper(Of TSource, TDestination)) As Integer _
                Implements System.IComparable(Of ObjectMapper _
                                             (Of TSource, TDestination)).CompareTo
        Return source.CompareTo(other.source)
    End Function
End Class

The TSource parameter is required to implement the IComparable interface so that an object of that type can be compared to another object of the same type. This is used in the CompareTo, which implements the IComparable interface for the ObjectMapper class, to compare the two source objects. The TDestination parameter requires a constructor that takes no arguments. The constructor is changed so that instead of a Destination object being provided, it is created as part of the constructor.

This example covered interface and constructor constraints. The full list of constraints is as follows:

  • Base class: Constrains the Type parameter to be, or be derived from, the class specified.

  • Class or Structure: Constrains the Type parameter to be a class or a structure (a struct in C#).

  • Interface: Constrains the Type parameter to implement the interface specified.

  • Constructor: Constrains the Type parameter to expose a no-parameter constructor. Use the new keyword as the constraint.

Multiple constraints can be supplied by separating the constraints with a comma, as shown in these snippets:

C#

public class MultipleConstraintClass<T>
                                 where T: IComparable, new()
{...}

VB.NET

Public Class MultipleConstraintClass(Of T As {IComparable,new})
...
End Class

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

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