BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 2.0+

Generic Types

Microsoft introduced generics to the .NET framework with version 2.0. Generic programming allows highly reusable classes to be created without specifying the types that they operate upon. These types are only provided when the class is used.

Creating a Generic Class

To create a generic class, you simply add one or more type parameters to the class definition. When using several type parameters, separate them with commas. Type parameters are often named with a single, upper case letter. However, this is not a requirement and names can be made longer and more meaningful.

In this section we will create a simple generic class that holds a pair of values or objects. The held items will be of a type specified when the class is used so we will use a type parameter, in this case named "T". To declare the class, add the following code:

public class Pair<T>
{
}

Whenever you need to use a value that is of the undeclared type, it can now be referenced using T. For example, to add the properties that hold the two values, add the following code. Note that the types of the properties and their backing stores use the unspecified type T.

T _firstItem;
T _secondItem;

public T FirstItem 
{
    get { return _firstItem; }
    set { _firstItem = value; }
}

public T SecondItem
{
    get { return _secondItem; }
    set { _secondItem = value; }
}

When creating a constructor for a generic type, you can use the type parameter to define the types of incoming parameters where required.

public Pair(T firstItem, T secondItem)
{
    FirstItem = firstItem;
    SecondItem = secondItem;
}

Finally, we can add a method to the class that we will use to show the contents of the Pair<T>. The following overrides to ToString method to combine and return the values:

public override string ToString()
{
    return string.Format("{0} | {1}", _firstItem, _secondItem);
}

You can now use the Pair class with any type. The sample below shows the results of Pair instances containing integers and strings.

Pair<int> integers = new Pair<int>(1, 2);
Console.WriteLine(integers);

Pair<string> strings = new Pair<string>("Hello", "world");
Console.WriteLine(strings);

/* OUTPUT

1 | 2
Hello | world

*/

Generic Constraints

The Pair class is a useful example of a generic type. However, it has one key problem. If you use Intellisense in Visual Studio you may have noticed that the "T" type has very few available members and that these are the methods and properties of a basic object. This means that T may represent any object but that the operations available to those objects are limited. You can overcome this by casting the T objects to other types but if you do, the type safety is lost. You could inadvertently create an instance of Pair for a type that does not support this cast and cause run-time exceptions.

When you need access to other members of a generic type, you can apply generic constraints to a type parameter. These constraints ensure that a class or structure that does not meet your requirements cannot be used for a type parameter.

There are three types of generic constraint. These are:

  • Derivation constraints.
  • Default constructor constraints.
  • Value / reference type constraints.

Derivation Constraints

A derivation constraint specifies that the type specified for a type parameter must derive from a supplied base class and / or implement one or more specified interfaces. When a derivation constraint is applied, all of the members of the provided base class and interfaces can be used for all objects of the type parameter. For example, if the Pair class needed access to the Dispose method provided by IDisposable, adding a derivation constraint for IDisposable would make the Dispose method be available and be displayed in Intellisense.

To add a derivation constraint to a type parameter, you use the following syntax:

class class-name<T> where T : base-class, interface-1, ..., interface-X

The base-class element is optional and is used when you want the type parameter to be a subclass of a specified base class. As classes may only inherit from one base class, only one base class can be specified here and must be the first item in the list. Any number of interfaces may be included. Note that the types used in the generic class must inherit from any base class specified and must implement all listed interfaces. If you are using several type parameters, each may include its own constraints.

To ensure that the Pair class is used with types that implement IDisposable, the declaration can be updated as follows:

public class Pair<T> where T : IDisposable

The T type now includes the members of the IDisposable interface, permitting an additional method that calls Dispose to be included in the class:

public void DisposeItems()
{
    FirstItem.Dispose();
    SecondItem.Dispose();
}

You will find that the test code that uses the Pair class is no longer valid as the int and string types do not implement IDisposable. Try using a type that implements IDisposable before removing the constraint and the DisposeItems method.

11 April 2010