Previous Section  < Day Day Up >  Next Section

3.10. Generics

To understand and appreciate the concept of generics, consider the need to create a class that will manage a collection of objects. The objects may be of any type and are specified at compile time by a parameter passed to the class. Moreover, the collection class must be type-safe梞eaning that it will accept only objects of the specified type.

In the 1.x versions of .NET, there is no way to create such a class. Your best option is to create a class that contains an array (or other container type) that treats everything as an object. As shown here, casting, or an as operator, is then required to access the actual object type. It is also necessary to include code that verifies the stored object is the correct type.


Object[] myStack = new object[50];

myStack[0] = new Circle(5.0);    // place Circle object in array

myStack[1] = "Circle";           // place string in array

Circle c1 = myStack[0] as Circle;

if( c1!=null) {                  // circle object obtained

Circle c2 = (Circle) myStack[1]; // invalid case exception


Generics, introduced with .NET 2.0, offer an elegant solution that eliminates the casting, explicit type checking, and boxing that occurs for value type objects. The primary challenge of working with generics is getting used to the syntax, which can be used with a class, interface, or structure.

The best way to approach the syntax is to think of it as a way to pass the data type you'll be working with as a parameter to the generic class. Here is the declaration for a generic class:


public class myCollection<T>

{

   T[] myStack = new T[50];

}


The type parameter T is placed in brackets and serves as a placeholder for the actual type. The compiler recognizes any reference to T within the body of the class and replaces it with the actual type requested. As this statement shows, creating an instance of a generic class is straightforward:


myCollection <string> = new myCollection<string>;


In this case, string is a type argument and specifies that the class is to work with string types only. Note that more than one type parameter may be used, and that the type parameter can be any name, although Microsoft uses (and recommends) single characters in its generic classes.

Although a class may be generic, it can restrict the types that it will accept. This is done by including an optional list of constraints for each type parameter. To declare a constraint, add the where keyword followed by a list of parameter/requirement pairs. The following declaration requires that the type parameter implement the ISerializable and IComparable interfaces:


public class myCollection<T> where

   T:ISerializable,

   T:IComparable


A parameter may have multiple interface constraints and a single class restraint. In addition, there are three special constraints to be aware of:

class? Parameter must be reference type.

struct? Parameter must be value type.

new()? Type parameter must have a parameterless constructor.

An example of a generic class is provided in Listing 3-16. The class implements an array that manages objects of the type specified in the type parameter. It includes methods to add items to the array, compare items, and return an item count. It includes one constraint that restricts the type parameter to a reference type.

Listing 3-16. A Generic Class to Hold Data

using System.Collections.Generic

public class GenStack<T>

   where T:class    // constraint restricts access to ref types

{

   private T[] stackCollection;

   private int count = 0;

   // Constructor

   public GenStack(int size)

   {

      stackCollection = new T[size];

   }

   public void Add(T item)

   {

      stackCollection[count] = item;

      count += 1;

   }

   // Indexer to expose elements in internal array

   public T this[int ndx]

   {

      get

      {

         if (!(ndx < 0 || ndx > count - 1))

            return stackCollection[ndx];

         // Return empty object

         else return (default(T));

      }

   }

   public int ItemCount

   {

      get {return count;}

   }

   public int Compare<C>(T value1, T value2)

   {

      // Case-sensitive comparison: -1, 0(match), 1

      return Comparer<T>.Default.Compare(value1, value2);

   }

}


The following code demonstrates how a client could access the generic class described in Listing 3-16:


// Create instance to hold 10 items of type string

GenStack<string> myStack = new GenStack<string>(10);

myStack.Add("Leslie");

myStack.Add("Joanna");

Console.WriteLine(myStack.ItemCount);  // 2

Console.WriteLine(myStack[1]);         // Joanna

int rel = myStack.Compare<string>("Joanna", "joanna");  // -1

Console.WriteLine(rel.ToString());


Generics and the .NET generic collection classes are discussed further in Chapter 4.

    Previous Section  < Day Day Up >  Next Section