C# Generics

C# has two separate mechanisms for writing code that is reusable across different types: inheritance and generics. Whereas inheritance expresses reusability with a base type, generics express reusability with a "template" that contains "placeholder" types. Generics, when compared to inheritance, can increase type safety and reduce casting and boxing.

Generic Types
A generic type declares generic parameters—placeholder types to be filled in by the consumer of the generic type, who will supply the generic arguments. Here is a generic type Stack<T>, designed to stack instances of type T. Stack<T> declares a single generic parameter T:

public class Stack<T>
{
int position;
T[] data = new T[100];
public void Push (T obj) { data[position++] = obj; }
public T Pop() { return data[--position]; }
}

We can use Stack<T> as follows:

Stack <int> stack = new Stack <int> ();
stack. Push(5);
stack. Push(10);
int x = stack.Pop(); // x is 10
int y = stack.Pop(); // y is 5

Stack<int> fills in the generic parameter T with the generic argument int, implicitly creating a type on the fly (the synthesis occurs at runtime). Stack<int> effectively has the following definition (substitutions appear in bold, with the class name hashed out to avoid confusion):

public class ###
{
int position;
int[] data;
public void Push (int obj) { data[position++] = obj; }
public int Pop() { return data[--position];}
}

Technically, we say that Stack<T> is an open type, whereas Stack<int> is a closed type. You can only instantiate a closed type, because all the placeholder types must be filled in.



Why Generics Exist
Generics exist to write code that is reusable across different types. Suppose we needed a stack of integers, but we didn't have generic types. One solution would be to hardcode a separate version of the class for every required element type (e.g., IntStack, StringStack, etc.) Clearly, this would cause considerable code duplication. Another solution would be to write a stack that is generalized by using object as the element type:

public class ObjectStack
{
int position;
object[] data = new object[10];
public void Push (object obj){data[position++] = obj;}
public object Pop() { return data[--position];}
}

An ObjectStack, however, wouldn't work as well as a hardcoded IntStack for specifically stacking integers. Specifically, an ObjectStack would require boxing and downcasting that could not be checked at compile time:

// Suppose we just want to store integers here:
ObjectStack stack = new ObjectStack();

stack.Push ("s"); // Wrong type, but no error!
int i = (int)stack.Pop(); // Downcast - runtime error

What we need is both a general implementation of a stack that works for all element types, and a way to easily specialize that stack to a specific element type for increased type safety and reduced casting and boxing. Generics give us precisely this, by allowing us to parameterize the element type. Stack<T> has the benefits of both ObjectStack and IntStack. Like ObjectStack, Stack<T> is written once to work generally across all types. Like IntStack, Stack<T> is specialized for a particular type—the beauty is that this type is T, which we substitute on the fly.


Generic Methods
A generic method declares generic parameters within the signature of a method.
With generic methods, many fundamental algorithms can be implemented in only a general-purpose way. Here is a generic method that swaps two values of any type:

static void Swap<T> (ref T a, ref T b)
{
T temp = a; a = b; b = temp;
}

Swap<T> can be used as follows:

int x = 5, y = 10;
Swap (ref x, ref y);

Generally, there is no need to supply type parameters to a generic method, because the compiler can implicitly infer the type. If there is ambiguity, generic methods can be called with the type parameters as follows:

Swap<int> (ref x, ref y);

Within a generic type, a method is not classed as generic unless it introduces generic parameters (with the angle bracket syntax). The Pop method in our generic stack merely uses the type's existing generic parameter, T, and is not classed as a generic method.
Methods and types are the only constructs that can introduce generic parameters. Properties, indexers, events, fields, methods, operators, and so on cannot declare generic parameters, although they can partake in any generic parameters already declared by their enclosing type. In our generic stack example, for instance, we could write an indexer that returns a generic item:

public T this [int index] { get {return data [index];} }


Declaring Generic Parameters
Generic parameters can be introduced in the declaration of classes, structs, interfaces, delegates, and methods. Other constructs, such as properties, cannot introduce a generic parameter, but can use a generic parameter. For example, the property Value uses T:

public struct Nullable<T>
{
public T Value {get;}
}

A generic type or method can have multiple parameters. For example:

class Dictionary<TKeyType, TValueType> {...}

To instantiate:

Dictionary<int,string> myDic =
new Dictionary<int,string>();

or (in C# 3.0):

var myDic = new Dictionary<int,string>();

Generic type names and method names can be overloaded as long as the number of generic parameters is different. For example, the following two type names do not conflict:

class A<T> {}
class A<T1,T2> {}


typeof and Generics
The typeof operator requires specifying the number of parameters when asking for the type of an open type, as follows:

class A<T> {}
class A<T1,T2> {}
...

Type a1 = typeof(A<>);
Type a2 = typeof(A<,>);

Here is an example of asking for the type of a closed type:

Type a3 = typeof(A<int,int>);


The default Generic Value
The default keyword can be used to get the default value given a generic type argument. The default value for a reference type is null, and the default value for a value type is the result of bitwise-zeroing the value type's fields:

static void Zap<T> (T[] array)
{
for (int i = 0; i < array.Length; i++)
array[i] = default(T);
}


Generic Constraints
By default, a generic parameter can be substituted with any type whatsoever. Constraints can be applied to a generic parameter to require more specific type arguments. These are the possible constraints:

where T : base-class // Base class constraint
where T : interface // Interface constraint
where T: class // Class constraint
where T : struct // Struct constraint
where T : new() // Parameterless constructor
// constraint
where U : T // Naked type constraint

In the following example, GenericClass<T> requires T to derive from SomeClass and implement Interface1:

class SomeClass {}
interface Interface1 {}

class GenericClass<T> where T : SomeClass, Interface1 {}

Constraints can be applied wherever generic parameters are defined, in both methods and type definitions.

A base class constaint or interface constraint specifies that the type parameter must subclass or implement a particular class or interface. This allows instances of that type to be implicitly cast to that class or interface. For example, suppose we want to write a generic Max method that returns the maximum of two values. We can take advantage of the generic interface defined in the System namespace IComparable<T>:

public interface IComparable<T>
{
int CompareTo (T other);
}

CompareTo returns a positive number if other is greater than this. Using this interface as a constraint, we can write a Max method as follows (to avoid distraction, null checking is omitted):

static T Max <T> (T a, T b) where T : IComparable<T>
{
return a.CompareTo (b) > 0 ? a : b;
}

The Max method can accept arguments of any type implementing IComparable<T> (which includes most built-in types such as int and string):

int z = Max (5, 10); // 10
string last = Max ("ant", "zoo"); // zoo

The class constraint and struct constraint simply specify that T must be a class or a struct. A great example of the struct constraint is the System.Nullable<T> struct :

struct Nullable<T> where T : struct {...}

The parameterless constructor constraint requires T to have a public parameterless constructor. If this constraint is defined, you can call new() on T:

static void Initialize<T> (T[] array) where T : new()
{
for (int i = 0; i < array.Length; i++)
array[i] = new T();
}

The naked type constraint requires one generic parameter to derive from another generic parameter. In this example, the method FilteredStack returns another Stack, containing only the subset of elements where the generic parameter T is of the generic parameter U:

class Stack<T>
{
Stack<U> FilteredStack<U>() where U : T {...}
}


Generics and Covariance
Generic types are not covariant. This means that even if B can be cast to A, T<B> cannot be cast to T<A>. For example, suppose Animal and Bear are defined as follows:

class Animal {}
class Bear : Animal {}

The following is illegal:

Stack<Bear> bears = new Stack <Bear>();

// compile-time error
Stack<Animal> animals = bears;

Lack of covariance can hinder reusability. Suppose, for instance, we wanted to write a method to Wash a stack of animals:

public class ZooCleaner
{
public static void Wash (Stack<Animal> animals) {...}
}

Calling Wash with a stack of bears would generate a compile-time error. The workaround is to redefine the Wash method with a constraint:

public class ZooCleaner
{
public static void Wash<T> (Stack<T> animals)
where T : Animal {}
}

We can now call Wash as follows:
Stack<Bear> bears = new Stack<Bear>();
ZooCleaner.Wash (bears);


Subclassing Generic Types
A generic class can be subclassed just like a nongeneric class. The subclass can leave the base class's generic parameters open, as in the following example:

class Stack <T> {...}
class SpecialStack <T> : Stack <T> {...}

Or the subclass can close the generic type parameters with a concrete type:

class IntStack : Stack<int> { ... }

A subclass can also introduce fresh generic arguments:

class Single<T> { ... }
class Double<T,U> : Single<T> { ... }


Self-Referencing Generic Declarations
A type can name itself as the concrete type when closing a generic argument:

public interface IEquatable<T> { bool Equals (T obj); }

public class Balloon : IEquatable<Balloon>
{
string color;
int cc;

public bool Equals (Balloon b)
{
if (b == null) return false;
return b.color == color && b.cc == cc;
}
}


Static Data
Static data is unique for each closed type:

class Bob<T> { public static int Count; }

class Test
{
static void Main()
{
Console.WriteLine (++Bob<int>.Count); // 1
Console.WriteLine (++Bob<int>.Count); // 2
Console.WriteLine (++Bob<string>.Count); // 1
Console.WriteLine (++Bob<object>.Count); // 1
}
}
}


Generic Collection Initialization
You can instantiate and populate a generic collection in a single step, as follows:

using System.Collections.Generic;
...

List<int> list = new List<int> {1, 2, 3};

The compiler translates this to:

using System.Collections.Generic;
...

List<int> list = new List<int>();
list.Add (1); list.Add (2); list.Add (3);

This requires that the collection implements the ICollection<T> interface, defined in System.Collections.Generic—the standard .NET interface for mutable collections.

0 comments


Subscribe to Developer Techno ?
Enter your email address:

Delivered by FeedBurner