Generics is a new feature in C# 2.0. Generics allow you to define type-safe data structures without specifying an actual data type. Generics are similar to C++ templates in concept, but are very different in implementation and capabilities.
To see what problems Generics solve, consider a very simple data structure such as the stack, providing the basic Push() and Pop() operations. Typically, you would like each stack instance to store instances of various types. For example, you may need a stack of ints, a stack of strings and so on. In C# 1.0 you have to use an object-based stack, meaning that the internal data type used in the stack is an instance of object and methods must interact with object instances:
// Sample implementation
public class Stack
{
object[] m_aItems = null;
public void Push( object item )
{ ... }
public object Pop()
{ ... }
}
// Sample Usage
Stack s = new Stack();
s.Push( 1 );
s.Push( 2 );
int n = (int)s.Pop();
There are two main problems in this approach:
Performance. When using value types, they have to be boxed
in order to push and store them as objects (a
reference type), and they have to be unboxed when popping them off the stack.
Boxing and unboxing incur a significant overhead as boxing allocates a new
object on the heap which increases the pressure on the managed heap resulting
in more garbage collections, which obviously affects performance. Even when
using reference types, there is still a performance hit as they have to be
casted up to an object when pushing and down to
the original type when popping.
Type-safety. This is a more severe problem because the compiler allows you to cast any type to and from object. You therefore lose compile-time type-safety. For example, the following code compile fine (when it should not) and raises an InvalidCastException at runtime:
Stack stack = new Stack();
stack.Push( 1 );
string s = (string)stack.Pop(); //
InvalidCastException. We should cast to an int.
In C# 1.0, you can overcome these two problems by providing a type-specific (and hence type-safe) stack. For example, you would have to implement an IntStack for integers and StringStack for strings:
// Sample implementation
public class IntStack
{
int[] m_aItems = null;
public void Push( int item )
{ ... }
public int Pop()
{ ... }
}
// Sample implementation
public class StringStack
{
string[] m_aItems = null;
public void Push( string item )
{ ... }
public string Pop()
{ ... }
}
And so on. Obviously, while this approach solves the performance and type-safety problems, it does introduce a third and more serious problem - productivity impact. Writing type-safe data structures is a tedious, repetitive and error-prone task. Enter Generics.
Generics allows you to define type-safe classes without compromising type-safety, performance, or productivity. The main approach (just as in C++), you implement a generic data structure only once, and then instantiate it with a specific data type. Classes, interfaces, structures, delegates, instance and static methods can all be generic types. Here is how you define and use a generic Stack:
public class Stack<T>
{
T[] m_aItems = null;
public void Push(T item)
{ /* ... */ }
public T Pop()
{ /* ... */ }
}
Stack<int> stack = new Stack<int>();
// Note syntax to tell the compiler which type to use
instead of the generic type parameter T
stack.Push( 10 );
// VS.NET 2005 Intellisense shows that Push() expects an
int parameter
stack.Push( 20 );
int n1 = stack.Pop();
// VS.NET 2005 Intellisense shows that Pop() returns an
int
int n2 = stack.Pop();.
Generics have native support in IL and the CLR. When you compile generics C# code, the compiler converts it to IL just like any other compiled code, however, the IL will contain placeholders for the actual specific types. In addition, the metadata for the generic code contains generic information. When a program provides a specific type instead of the generic type parameter T, the specific type will replace the generic type parameter T as if generics were never involved.
Generics should be used whenever a data structure or a utility class can be expressed in a generic version, and not the object-based version. Typically, collections and data structures such as linked lists, queues, binary trees etc will offer generic support, but generics are not limited to data structures. Often, utility classes such as class factories or formatter can take advantage of generics. However, note that generics are not necessarily faster that non-generic code. If the non-generic code is using instances of object as the containers, then generics are definitely faster, but if the non-generic code is using type-specific data structures, then there is possibly no performance benefit to generics (problems relating to type-safety and productivity still apply though).
Note on naming Generics conventions which are used throughout:
T is the generic type parameter. In other words, a generic type parameter is a placeholder for types.
Stack<T> is the generic type.
int in Stack<int> is the type argument. In other words, the type argument is the type the client specifies to use instead of the generic type parameter.
Because of the native support for generics in IL and the CLR, most CLR-compliant languages can take advantage of generic types. The following simple examples show how to apply generics:
// Example 1
internal class MyPoint<T>
{
private T m_x;
private T m_y;
public MyPoint()
{
m_x = default(T);
// default keyword is specific to generics and returns the
default value of the specified type
m_y = default(T);
// default keyword is specific to generics and returns the
default value of the specified type
}
public MyPoint(T x, T y)
{
m_x = x;
m_y = y;
}
public T X
{
get { return m_x; }
set { m_x = value; }
}
public T Y
{
get { return m_y; }
set { m_y = value; }
}
}
MyPoint<int> intPt = new MyPoint<int>();
intPt.X = 10;
intPt.Y = 20;
Trace.WriteLine(intPt.X + "," + intPt.Y);
MyPoint<double> dblPt = new MyPoint<double>();
dblPt.X = 10.0;
dblPt.Y = 20.0;
Trace.WriteLine(dblPt.X + "," + dblPt.Y);
Note: the default keyword returns the default value of the type argument. For value types, the default value corresponds to filling the struct (all value types are structs) with zeros. For reference types, the default value is null.
// Example 2
// This class represents a node whose types are generic
internal class Node<K, I>
{
public K
key;
public I
item;
public Node<K, I> nextNode;
public Node()
{
key =
default(K);
// Note use of default
item =
default(I);
// Note use of default
nextNode = null;
}
public Node(K k, I i, Node<K, I> next)
{
key =
k;
item = i;
nextNode = next;
}
}
// This class represents a linked list whose node types
are generic. The fact the LinkedList uses the same names
// as the node for the generic type arguments is purely for readability
purposes. Other names for K and I could
// have been used.
internal class LinkedList<K, I>
{
internal Node<K, I> m_head;
public LinkedList()
{
// We must
provide type arguments when instantiating a generic type. Note how we use the
linked list's
// own generic type arguments
m_head = new Node<K,
I>();
}
public void AddHead(K k, I i)
{
// Again, we
must provide type arguments when instantiating a generic type. Note how we use
the linked list's
// own generic type arguments
Node<K, I> node = new
Node<K, I>(k, i, m_head.nextNode);
m_head.nextNode = node;
}
}
LinkedList<int, string> list = new LinkedList<int,
string>();
list.AddHead(10, "Ten");
list.AddHead(20, "Twenty");
LinkedList<double, DateTime> list2 = new LinkedList<double, DateTime>();
list2.AddHead(10.0, DateTime.Now.AddDays(1));
list2.AddHead(20.0, DateTime.Now.AddDays(2));
using List = LinkedList<int, string>;
// Providing an alias for a particular combination of
types.
// The scope of aliasing is the scope of the file
List list = new List();
list.AddHead( 10, "hello" );
Note the following naming conventions for generic types (assume the generic type is LinkedList<K,I>):
LinkedList<int,string> is a closed constructed type, or simple a constructed type.
LinkedList<int, I>, LinkedLinst<K, string> and LinkedList<K,I> are all called open constructed types.
Generic constraints solve the following problem: Generic code can contain code that is incompatible with the specified type argument. Generic constraints imposes certain constraints or limitations on the type arguments in order to ensure that the generic code behaves correctly. For example, a generic function inside a generic class may need to call ICloneable.Clone on the type argument, meaning that the generic function assumes that the type argument implements ICloneable. If the supplied type argument did not implement ICloneable, a runtime exception will be generated and the code fails. Generic constraints solve these kinds of problems.
In fact, there are three types of generic constraints:
Derivation Constraint: This indicates to the complier that the generic type parameter derives from a base type such as an interface or a base class.
Default Constructor Constraint: This indicates to the complier that the generic type parameter has a default constructor (a public constructor with no arguments.)
Reference/value type Constraint: This indicates to the complier that the generic type parameter is a reference or a value type.
It is important to note that while constraints are optional, they can be essential when developing a generic type. Without them, the compiler takes a more conservative and type-safe approach and only allows access to object-level functionality in your generic type parameters. Also note that constraints are part of the generic type metadata so that the compiler can take advantage of them. The compiler reads the constraints from the metadata and only allows type arguments that comply with the constraints, thus enforcing type safety.
This constraint uses the where keyword to indicate to the compiler that a generic type parameter derives from some base type. The compiler will generate an error (CS0309) if a type argument did not derive from the given base class. The following examples illustrate:
Example 1: Basic derivation constraint.
public interface I
{
/* ... */
}
public class A : I
{
/* ... */
}
public class B
{
/* ... */
}
public class C<T> where T : I //
Generic type parameter T must derive from interface I
{
/* ... */
}
C<A> c1 = new C<A>();
// A derives from interface I, hence code compiles.
C<B> c2 = new C<B>(); // B does not
derive from interface I, hence compiler error CS0309
Example 2: All derivation constraints must appear after the actual derivation list of the generic class.
public class C<T> : IEnumerable<T> where
T : I // C<T> derives from IEnumerable<T>.
Also generic type argument T must derive from interface I
{
/* ... */
}
Example 3: You can provide a derivation constraint for each generic type argument.
public class A<T, K> where T : MyBase
// A base class in a derivation constraint list cannot be
sealed or
// static. And there can only one base class.
where K : IComparable<K>
{ /* ... */ }
Example 4: You can constrain a base class and/or multiple interfaces on the same generic type argument. If a base class is specified, it must appear first in the derivation constraint list.
public class A<T,K> where T: MyBase,
IEnumerable, ICloneable<T>
{ /* ... */ }
Example 5: When dealing with a derivation constraint, the type argument can specify the base type itself, and not necessarily a derived class.
public interface IMyInterface
{ /* ... */ }
public class MyClass : IMyInterface
{ /* ... */ }
public class A<T> where T : IMyInterface
{ /* ... */ }
A<IMyInterface> a = new A<IMyInterface>();
This constraint uses the new keyword to indicate to the compiler that a generic type parameter supports a public default constructor (a constructor with no parameters). For example, suppose you want to instantiate inside the generic class a new object from the type argument. The compiler will generate error CS0304 if the type argument did not support a default constructor. The following example illustrates:
// Does not compile
public class D<T>
{
public T m_nID = new T();
// error CS0304: Cannot create an instance of the variable type 'T' because it
does not have the new() constraint
// ...
}
// Same example as above,
but uses the new constraint
public class D<T> where T : new()
{
public T m_nID = new T();
// OK
// ...
}
// Same example as above
but combining the default constructor constraint with a derivation constraint
(default
// constructor constraint must appear last)
public class D<T> where T : SomeBase, new()
{
/* ... */
}
This constraint uses the struct/class keywords to indicate to the compiler that a generic type parameter is a value type/reference type, respectively. The following example illustrates:
public class A<T> where T : struct
// T must be a value type
{ /* ... */ }
public class B<T> where T : class
// T must be a reference type
{ /* ... */ }
A<int> a1 = new A<int>();
// OK. Type argument is value-type
B<string> b1 = new B<string>();
// OK. Type argument is reference-type
A<string> a2 = new A<string>();
// error CS0453: The type 'string' must be a non-nullable
value type in order
// to use it as parameter 'T' in the generic type or
method 'Generics.A<T>'
Note that reference/value constraint cannot be used with a base class constraint, but can be combined with any other constraint. When used, a reference/value constraint must appear first.
Note the following points:
C# compiler allows you to implicitly cast generic type parameters to object or to constraint-specified types. Such casting is type-safe because any incompatibility is discovered at compile-time:
internal interface IMyInterface
{
void foo();
}
internal class MyBaseClass
{
public void bar() { /* ... */ }
}
internal class MyClass<T> where T : MyBaseClass, IMyInterface
{
public void SomeMethbod(T t)
{
// Can
implicitly cast to a constraint-specified interface
IMyInterface itf = t;
// Can
implicitly cast to a constraint-specified class
MyBaseClass c = t;
// Can
implicitly cast to object
object o = t;
}
}
internal interface IMyInterface
{
void foo();
}
internal class MyBaseClass
{
public void bar() { /* ... */ }
}
internal class MyClass<T>
// Note this class has no derivation-constraints
{
public void SomeMethod(T t)
{
IMyInterface
itf = t;
// error CS0266: Cannot implicitly convert type 'T' to
'Generics.Casting2.IMyInterface'.
// An explicit conversion exists (are you missing a cast?)
MyBaseClass mbc = t;
// error CS0029: Cannot implicitly convert type 'T' to
'Generics.Casting2.MyBaseClass'
IMyInterface itf2 = (IMyInterface)t;
// OK
MyBaseClass mbc2 = ((MyBaseClass)(object)t);
// OK
}
}
Explicit casting is dangerous as it may throw an exception at run time if the type argument does not derive from the type you wish to cast to. A better approach is to use the is and as operators:
internal interface IMyInterface
{
void foo();
}
internal class MyBaseClass
{
public void bar() { /* ... */ }
}
internal class MyClass<T>
{
public void SomeMethod(T t)
{
// Using 'is'
IMyInterface itf = null;
if ( t is IMyInterface)
itf = (IMyInterface)t;
// Using 'as'
MyBaseClass mbc = t as
MyBaseClass;
if (mbc != null)
mbc.bar();
}
}
Note the following points:
internal class MyGenericBaseClass<T>
{ /* ... */ }
internal class MyClass : MyGenericBaseClass<int> {
/* ... */ }
When deriving from a generic class, if the sub-class is generic too, you can use the subclass generic type parameter for the generic base class:
internal class MyGenericBaseClass<T>
{ /* ... */ }
internal class MyGenericSubClass<T> : MyGenericBaseClass<T>
{ /* ... */ }
When deriving from a generic class, if the sub-class is generic too and the base class specifies constraints, you must repeat any base-class constraints:
internal class MyGenericBaseClass2<T> where T
: ICloneable
{ /* ... */ }
internal class MyGenericSubClass2<T> : MyGenericBaseClass2<T> where T :
ICloneable { /* ... */ }
When deriving from a generic class, a type argument must be provided instead of the generic type parameter. And if the base generic class has a virtual method whose signature uses generic type parameters, the overridden method in the derived class must provide the corresponding types in the method signature:
internal class MyGenericBaseClass3<T>
{
public virtual void SomeMethod(T t) {
/* ... */ }
}
internal class MyClass2 : MyGenericBaseClass3<int>
{
public override void SomeMethod(int n)
{
{ /* ... */}
}
}
You can define generic interfaces, generic base classes, and generic abstract methods:
internal interface IMyInterface<T>
{
T SomeMethod(T t);
}
internal abstract class MyAbstractGenericClass<T>
{
public abstract void SomeMethod(T t);
}
internal class MyDerivedGenericClass<T> : MyAbstractGenericClass<T>
{
public override void SomeMethod(T t) { /* ... */ }
}
You cannot use + or += operators on generic type parameters. The recommended solution is to compensate using interfaces (recommended solution) or abstract classes:
public class Calculator<T>
{
public T Add(T lhs, T rhs)
{
return lhs +
rhs;
// error CS0019: Operator '+' cannot be applied to
operands of type 'T' and 'T'
}
}
public interface ICalculator<T>
{
T Add(T lhs, T rhs);
}
public class Calculator2 : ICalculator<int>
{
public int Add(int lhs, int rhs)
{
return lhs + rhs;
}
}
The following example illustrates issues relating to instance generic methods:
internal class MyClass<T>
{
private T m_t = default(T);
// No need to specify <T> here. In
fact, specifying <T> for Method gives warning
CS0693: Type parameter 'T'
// has the same name as the type parameter from outer type 'MyClass<T>'
public void Method<T>( T t )
{
Trace.WriteLine("t = " + t);
}
// This method has a generic type
parameter similar to that of the containing class.
// Note that you cannot provide method-level constraints for
class-level generic type
// parameters
// public void Method1(T t) where T : new
// Does NOT compile
public void Method1(T t)
{
Trace.WriteLine("t = " + t);
}
// A method can also define its own
generic type parameters that are different from
// those defined at the class level
public void Method2<S>(S s, T t)
{
Trace.WriteLine("s = " + s + " t = "
+ t);
}
// When a method defines its own
generic type parameter, the method can define
// constraints for these types as well
public void Method3<W>(W w) where W : struct
{
Trace.WriteLine("w = " + w);
}
// The ability to define generic type
parameters at the method level cannot be
// applied to properties and indexers. Properties and
indexers can only use generic
// type paramters defined at the class level. The following
does not compile
// public X ID<X>
// {
// get { return default(X); }
// }
public T ID
{
get { return m_t; }
set { m_t = value; }
}
}
// When calling a method that
defines its own generic type parameter, you can either provide the
// type as is done in the first call, or you can let the compiler infer the type
based on the
// type of the parameter that was passed in (this compiler ability is called
'generic type inference'.
// Both calls below to Method2 compile
MyClass<int> obMyClass = new MyClass<int>();
obMyClass.Method2<string>("hello", 20);
// OK
obMyClass.Method2("inferred", 30);
// OK
// Calling a method that defines constraints on its own
generic type parameters
obMyClass.Method3<double>(10.0);
Another example relating to instance generic methods but for subclasses as well:
internal class MyClass2
// Not a generic class!
{
// You can define generic methods even
if the class does not use generics at all
public virtual void Method1<T>(T t) where T :
struct
{
Trace.WriteLine(" t = " + t);
}
}
internal class MySubClass2 : MyClass2
{
// Sub-class implementation must
override the base virtual generic method but should not re-specify
// any constraints at all, else error CS0460 is generated:
'Constraints for override and explicit
// interface implementation methods are inherited from the
base method, so they cannot be specified
// directly'
public override void Method1<T>(T t)
//No constraints can be specified here
{
Trace.WriteLine(" t = " + t);
// If calling
the base class implementation, you can either specify the type argument
// or rely on type inference
base.Method1<T>(t);
// OK
base.Method1(t);
// OK
}
}
// When calling a method that
defines its own generic type parameter, you can either provide the
// type as is done in the first call, or you can let the compiler infer the type
based on the
// type of the parameter that was passed in (this compiler ability is called
'generic type inference'.
// Both calls below to Method2 compile
MyClass2 obMyClass2 = new MyClass2();
obMyClass2.Method1<int>(10);
// OK
obMyClass2.Method1(20);
// OK
In the example above, Method1 defines generic type parameters independently of its containing class. The main benefit is that you can call Method1 each time with a different type argument, without ever overloading the method.
The following example illustrates issues relating to static generic methods:
internal class MyClass<T>
{
// You can define static methods that
use generic type parameters
public static T SomeMethod(T t)
{
Trace.WriteLine("t = " + t);
return t;
}
// Just like instance methods, static
methods can define method-specific generic
// type parameters and constraints
public static void SomeMethod2<X>(X x, T t)
where X : class
{
Trace.WriteLine("x = " + x + " t = "
+ t);
}
}
// Calling a static generic method
int n = Generics.StaticMethods.MyClass<int>.SomeMethod(10);
// Calling a static generic method that defines
method-specific generic type parameters.
// Just like instance methods, you can either explicitly specify the type
argument or rely
// on type inference
MyClass<double>.SomeMethod2<string>("hello", 12.34); // OK
MyClass<double>.SomeMethod2("hello", 12.34);
// OK
MyClass<double>.SomeMethod2<int>(12, 12.34);
// error CS0452: The type 'int' must be a reference type
in order to use it as
// parameter 'X' in the generic type or method 'MyClass<T>.SomeMethod2<X>(X, T)'
The following example illustrates generic delegates:
internal class MyClass<T>
{
// Delegates can use generic type
parameters
public delegate void Start(T t);
public void StartMethod(T t)
{
Trace.WriteLine("t = " + t);
}
// Delegates can define their own
generic type parameters
public delegate void End<X>(X x, T t);
public void EndMethod<X>(X x, T t)
{
Trace.WriteLine("x = " + x + " t = "
+ t);
}
}
// Delegates defined outside the scope of a class can also use
generic type parameters
internal delegate void delStart<T>(T t) where T : class;
// Using a generic
delegate
MyClass<string> obMyClass = new MyClass<string>();
MyClass<string>.Start dStart1 = new MyClass<string>.Start(obMyClass.StartMethod);
dStart1("Event data");
// The following instantiation of Start delegate gives the
following error: error CS0123: No overload
// for 'Method1' matches delegate 'MyClass<int>.Start'. This is because
obMyClass was instantiated with
// a 'string' type argument, hence obMyClass.Method1(T t) expects a string
argument
MyClass<int>.Start dStart2 = new MyClass<int>.Start(obMyClass.Method1);
dStart2(20);
// In C# 2.0, you can make a direct assignment of a method
reference to a delegate variable
// (whether the delegate variable uses generics or not)
MyClass<string>.Start dStart3 = obMyClass.StartMethod;
// Using a generic delegate that defines its own generic
type parameters
MyClass<string>.End<double> dEnd1 = new MyClass<string>.End<double>(obMyClass.EndMethod<double>);
dEnd1(10, "hello");
// Using a delegate define outside the scope of a class.
Note that the type argument must
// be supplied for the delegate when declaring and instantiating it
delStart<string> dStart4 = new delStart<string>(obMyClass.StartMethod);
dStart4("hello");
Generic delegates are especially useful when it comes to events. You can literally define a limited set of generic delegates, distinguished only by the number of the generic type parameters they require, and use these delegates for all of your event handling needs. The following code illustrates the use of a generic delegate and a generic event-handling method:
// Generic event handling
internal delegate void GenericEventHandler<S, A>(S sender, A args) where A :
System.EventArgs; // Note
constraints
internal class Publisher
{
public event GenericEventHandler<Publisher,
System.EventArgs> MyEvent;
public void FireEvent()
{
MyEvent( this, new EventArgs());
}
}
internal class Subscriber
{
public void EventHandler(Publisher sender, EventArgs args)
{
Trace.WriteLine("Received event");
}
}
// Generic event handling
Publisher pub = new Publisher();
Subscriber sub = new Subscriber();
pub.MyEvent += new GenericEventHandler<Publisher,EventArgs>(sub.EventHandler);
pub.FireEvent();
.NET now supports generic type parameters (recall in Stack<T>, T is the generic type parameter, Stack<T> is the generic type and int in Stack<int> is the type argument). Most of generics support in reflection resolves around the Type class. In the following examples, any generic type with a specified type argument is called a bounded type, and a generic type with an unspecified type argument is called an unbounded type. For example, Stack<int> is a bounded type and Stack<> is an unbounded type.
GetType() method and typeof() operator now support generic types as follows:
public void TestType(T t)
{
// typeof() and GetType() can operate
on generic type parameters
Type tp1 = t.GetType();
// Note the use of 't' and not 'T'
Type tp2 = typeof(T);
// Note the use of 'T' and not 't'
Debug.Assert(tp1 == tp2);
Trace.WriteLine("tp1 full name = " + tp1.FullName +
" tp2 full name = " + tp2.FullName); //
Output: tp1 full name = System.Int32 tp2 full name = System.Int32
// typeof() and GetType() can operate
on bounded generic types
MyPoint<T> pt = new MyPoint<T>();
Type tp3 = pt.GetType();
// Note use of pt, an instance of a bounded generic type
Type tp4 = typeof( MyPoint<T> );
// Note use of MyPoint<T>
Debug.Assert(tp3 == tp4);
Trace.WriteLine("tp3 full name = " + tp3.FullName +
" tp4 full name = " + tp4.FullName); // Output: tp3
full name = Generics.MyPoint`1[[System.Int32, mscorlib,
// Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
// tp4 full name = Generics.MyPoint`1[[System.Int32, mscorlib, Version=2.0.0.0,
// Culture=neutral, PublicKeyToken=b77a5c561934e089]]
// typeof() can operator on unbounded
generic types. GetType() cannot be used because
// GetType() operates on an instance and it is a compile-time
error to get an instance of
// an unbounded generic type (i.e., instantiating MyPoint<>
gives a compile time error)
Type tpUnbounded1 = typeof(MyPoint<>);
Trace.WriteLine("Type of MyPoint<> is " +
tpUnbounded1.ToString()); //
Output: Type of MyPoint<> is Generics.MyPoint`1[T]
Type tpUnbounded2 = typeof(LinkedList<,>);
// Note use of <,>
Trace.WriteLine("Type of LinkedList<,> is " +
tpUnbounded2.ToString()); // Output: Type of LinkedList<,>
is Generics.LinkedList`2[K,I]
}
Type class has new methods and properties to provide reflection information about the generic aspect of a generic type:
public void TestType2(T t)
{
MyPoint<T> pt = new MyPoint<T>();
Type tpPoint = pt.GetType();
Type[] aGenericArguments = tpPoint.GetGenericArguments();
foreach (Type tp in aGenericArguments)
Trace.WriteLine(tp.FullName);
// Output: System.Int32
Type tpGenericTypeDef = tpPoint.GetGenericTypeDefinition();
Trace.WriteLine(tpGenericTypeDef.FullName);
// Output: Generics.MyPoint`1
}
And when defining an attribute (by deriving from System.Attribute), you can instruct the compiler that the attribute should target generic type parameters (i.e., the T in Stack<T>):
[AttributeUsage(AttributeTargets.GenericParameter)]
public class GenericParamAttribute : System.Attribute
{
/* ... */
}
Finally, note that you cannot define generic attributes, but an attribute class can take advantage of generics by using generic types or define helper generic methods just like any other class:
public class SomeAttribute<T>
: Attribute // error CS0698: A generic type
cannot derive from 'System.Attribute' because it is an attribute class
{ /* ... */ }
This section covers areas of the .NET Framework that are now using generics.
System.Array class has been extended with many generic static methods. These generic static methods are designed to streamline and automate common tasks of working with arrays. Such common tasks include:
System.Array's generic static methods all work with the following four generic delegates (defined in the System namespace):
public delegate void Action<T>(T objSource)
Action is a delegate to a method that performs an
action on the objects passed to it (objSource in
the code above). For example, in Array.ForEach,
the elements of the array are individually passed to the
Action delegate.
public delegate int Comparison<T>(T x, T y)
Comparison is a delegate to a method that compares
two objects. For example, in Array.Sort, elements
of the array are passed to the Comparison delegate
to perform a QuickSort sort algorithm.
public delegate TTarget Converter<TSource,
TTarget>(TSource source)
Converter is a delegate to a method that converts
a source object to a target object. For example,
in Array.ConvertAll, the elements of the array are
individually passed to the Converter, and the
converted elements are saved in a new array.
public delegate bool Predicate<T>(T
objSource)
Predicate is a delegate to a method that returns
true if the object passed to it (objSource in the
signature above) matches the conditions defined in the delegate method. For
example, in Array.FindAll, the elements of the
array are individually passed to the
Predicate and elements that match the predicate are returned in an
array.
The following examples illustrate the points above:
class Arrays<T> where T : class
{
private T[] m_aData = null;
// Initializes the array of generic
data
public Arrays(T[] data)
{
m_aData = data;
}
public void TestPredicate()
{
// In C# 2, you
can directly assign a delegate variable with a method reference without having
to use new
Predicate<T> delIsNonNull =
IsNonNull;
T[] aNonNulls = Array.FindAll<T>(m_aData,
delIsNonNull);
// aNonNulls contains 1, 2, 4
}
public void TestAction()
{
// In C# 2, you
can directly assign a delegate variable with a method reference without having
to use new
Action<T> delDoTrace = DoTrace;
Array.ForEach<T>(m_aData, delDoTrace);
}
public void TestConverter()
{
// In C# 2, you
can directly assign a delegate variable with a method reference without having
to use new
Converter<T, int> delConvertToInt =
Convert;
int[] aConvertedData =
Array.ConvertAll<T, int>(m_aData, delConvertToInt); // aConvertedData contains
1,2,0,4
}
// Predicate delegate function
private bool IsNonNull(T element)
{
bool bRet = (element != null) ? true
: false;
return bRet;
}
// Action delegate function
private void DoTrace(T element)
{
if (element != null)
Trace.WriteLine("element " + element.ToString());
}
// Converter delegate function
private int Convert(T source)
{
if (source != null)
return
System.Convert.ToInt32(source);
else
return 0;
}
}
string[] aData = { "1", "2", null, "4" };
Arrays<string> ob = new Arrays<string>(aData);
ob.TestPredicate();
ob.TestAction();
ob.TestConverter();
Similar generic methods are also available on the class List<T> defined in System.Collections.Generic namespace. These methods use the same four generic delegates mentioned above.
The data structures in System.Collections are all object-based and hence inheriting the two problems mentioned above, namely inferior performance and lack of type safety. This problem is alleviated with a set of generic collections from the System.Collections.Generic namespace. For example, now there are generic Stack<T>, Queue<T>, IEnumerable<T>, IEnumerator<T>, IList<T> and so on. Also among the generic collections is Dictionary<K,V> which is equivalent to the non-generic Hashtable class.
Note that all generic collections implement IEnumerable<T> which means that all generic collections can be be used in a C# foreach statement:
// Create a generic
dictionary with int and string type arguments
Dictionary<int, string> htID_To_Name = new Dictionary<int, string>();
htID_To_Name.Add(1, "generics");
htID_To_Name.Add(2, "ADO.NET");
htID_To_Name.Add(3, "iterators");
// Previously, to iterator over a dictionary you would use
the following statement:
// foreach (DictionaryEntry de in htIDToName ) { ... }
// Now note the usage of KeyValuePair instead
foreach (KeyValuePair< int, string> kvp in htID_To_Name)
{
Trace.WriteLine("Key: " + kvp.Key + ". Value: " + kvp.Value);
}
Generic types can be serialized, i.e. marked with the [Serializable] attribute:
[Serializable]
internal class Line<T>
{
public T X = default(T);
public T Y = default(T);
}
Serializing an instance of a type means persisting the state of that instance into some stream. However, for generic types, the following points must be observed:
In addition to state, serializing a generic type persists metadata about the generic instance and its type. Therefore, each permutation of a generic type is considered a unique type. For example, you cannot serialize an object of type Line<int> but deserialize it into an object of type Line<double>.
Serializing an instance of a generic type is no different than serializing an instance of a non-generic type. However, when deserializing an instance of a generic type you need to deserialize into a variable with matching specific types.
These two points are illustrated in the example below:
public void TestSerialization()
{
try
{
// Create a
bounded generic type (i.e., a generic type that is bounded to a type)
Line<int> line = new Line<int>();
line.X = 10;
line.Y = 20;
// Serialize
the bounded generic type
MemoryStream ms = new
MemoryStream();
IFormatter formatter = new
BinaryFormatter();
formatter.Serialize(ms, line);
// Deserialize
the generic type back to the original bounded generic type
ms.Seek(0, SeekOrigin.Begin);
Line<int> line2= (Line<int>)formatter.Deserialize(ms);
//
Deserializing into a different generic type raises an exception: Unable to cast
object of
// type
'Generics.Line`1[System.Int32]' to type 'Generics.Line`1[System.Double]'.
Line<double> line2= (Line<double>)formatter.Deserialize(ms);
// Error
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
In the code snippet above, IFormatter is object-based. You can implement a generic formatter to do away with casting and possible errors due to incorrect casting to different generic types:
internal interface IGenericFormatter<T>
{
void Serialize(Stream serializationStream, T graph);
T Deserialize(Stream serializationStream);
}
public class GenericFormatter<T, F> : IGenericFormatter<T> where F :
IFormatter, new()
{
IFormatter formatter = new F();
// IGenericFormatter implementation
public void Serialize(Stream serializationStream, T graph)
{
formatter.Serialize(serializationStream, graph);
}
public T Deserialize(Stream serializationStream)
{
return (T)formatter.Deserialize(serializationStream);
}
}
public void TestSerialization2()
{
try
{
// Create a
bounded generic type (i.e., a generic type that is bounded to a type)
Line<int> line = new Line<int>();
line.X = 10;
line.Y = 20;
// Serialize
the bounded generic type using a generic formatter
System.IO.MemoryStream ms = new
MemoryStream();
GenericFormatter<Line<int>,
BinaryFormatter> formatter = new GenericFormatter<Line<int>, BinaryFormatter>();
formatter.Serialize(ms, line);
// Deserialize
the generic type back to the original bounded generic type
ms.Seek(0, SeekOrigin.Begin);
Line<int> line2 =
formatter.Deserialize(ms);
// No casting necessary
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
As expected, you can define and deploy remotable classes that use generics:
public class MyServer<T> : MarshalByRefObject where T :
MarshalByRefObject
{
/* ... */
}
The derivation constraint is necessary because you can only access MyServer over remoting only when the generic type parameter T is a marshalable object.
As for configuration, you can use administrative or programmatic configuration as follows:
Here you need to specify the exact type arguments to use instead of the generic type parameters. For example, the following shows how to configure a client-activated object CAO called MyServer<T> declared in the namespace RemoteServer and implemented in ServerAssembly.dll to use int and string:
<service>
<activated type="RemoteServer.MyServer[[System.Int32],[System.String]],
ServerAssembly" />
</service>
Here you configure remotable object similar to .NET 1.1, except when defining the type of the remote object you have to provide type arguments instead of the generic type parameters:
RemotingConfiguration.RegisterWellKnownClientType( typeof(MyServer<int>), strURL );
And as for creating remotable objects, simply provide the type arguments:
// Using new
MyServer<int> ob = new MyServer<int>();
// Using
Activator.GetObject
MyServer<int> ob = (MyServer<int>)Activator.GetObject( typeof(MyServer<int>),
strURL );
Generic types are not covariant. A generic type with a specified type argument MyClass<MyDerived> cannot be substituted with another generic type MyClass<MyDerived> that uses a type argument that is the base type for the first type argument. For example, the following does not compile:
class MyBase
{ /* ... */ }
class MyDerived : MyBase { /* ... */ }
class MyClass<T> {
/* ... */ }
MyClass<MyBase> ob = new MyClass<MyDerived>(); // error CS0029: Cannot implicitly convert type 'MyClass<MyDerived>' to 'MyClass<MyBase>'
However, generic constraints are covariant. Consider this example:
class MyBase
{ /* ... */ }
class MyDerived : MyBase
{ /* ... */ }
class MyClass<T> where T : MyBase { /* ... */
}
MyClass<MyDerived> ob = new MyClass<MyDerived>(); // Compiles because MyDerived inherits from MyBase
Generic types are not contra-variant. For example, it is true that MyClass<MyBase> is not the base type of MyClass<MyDerived>:
Debug.Assert( typeof(MyClass<MyBase>) != typeof(MyClass<MyDerived>).BaseType ); // Assertion is true
Finally, generics are invariant because there is no relationship between two generic types with different type arguments even if those type arguments do have an is-a relationship. For example, List<int> has nothing to do with List<object> even though an int is-a object.
A generic class cannot derive from its type parameter:
public class MyClass<T> : T
// error CS0689: Cannot derive from 'T' because it is a
type parameter
{ /* ... */ }
Generics are similar in concept to C++ templates: both allow clients to specify types for data structures or utility classes. Also both offer productivity and type-safety benefits. However, there are two main differences between generics and C++ templates:
Underlying implementation
Both can templates and generics can incur some code bloat and both have
mechanism to limit that bloat. In C++ templates, instantiating a template with
a set of types instantiates only the methods actually used. All other methods
that result in identical code are automatically merged by the compiler to
prevent duplication. With generics, instantiating a generic type with a set of
types instantiates all of its methods, but only once for all reference type
arguments. Bloat in generics only comes from value types because the CLR
instantiates a generic type separately for each value type argument.
Interfaces and generics serve different purposes. Interfaces are about defining a contract between service a consumer and a service provider. As long as the consumer programs against the interface, it can use any other service provider that supports the same interface. This allows switching service providers without affecting client code.
On the other hand, generics are about defining and implementing a service without committing to the actual types used. As such, interfaces and generics are not mutually exclusive, in fact they complement each other. Good programming practices often combine interfaces and generics. For example, consider the IList<T> interface:
public interface IList<T>
{
void Insert( int index, T item );
void RemoveAt( int index );
T this[int index] { get; set; }
}
IList<T> can then be implemented by any class:
public class SortedList<T> : IList<T> { /* ... */ }
public class LinkedList<T> : IList<T> { /* ... */ }
You can now program against IList<T> using both different implementations and different type arguments:
IList<int> list1 = new
SortedList<int>();
IList<double> list2 = new LinkedList<double>();