an introduction to c# generics
juval lowy
august 2003
summary: this article discusses the problem space generics address, how they are implemented, the
benefits of the programming model, and unique innovations, such as constrains, generic methods and
delegates, and generic inheritance. (41 pages)
download the genericsincsharp.msi sample file.
note this article assumes you are familiar with c# 1.1. for more information on the c# language, visit
http://msdn.microsoft.com/vcsharp/language.
contents
introduction
generics problem statement
what are generics
applying generics
generic constraints
generics and casting
inheritance and generics
generic methods
generic delegates
generics and reflection
generics and the .net framework
conclusion
introduction
generics are the most powerful and anticipated feature of c# 2.0. generics allow you to define type-safe
data structures, without committing to actual data types. this results in a significant performance boost
and higher quality code, because you get to reuse data processing algorithms without duplicating
type-specific code. in concept, generics are similar to c++ templates, but are drastically different in
implementation and capabilities. this article discusses the problem space generics address, how they are
implemented, the benefits of the programming model, and unique innovations, such as constrains,
generic methods and delegates, and generic inheritance. you will also see how generics are utilized in
other areas of the .net framework such as reflection, collections, serialization, and remoting, and how
to improve on the basic offering.
generics problem statement
consider an everyday data structure such as a stack, providing the classic push() and pop() methods.
when developing a general-purpose stack, you would like to use it to store instances of various types.
under c# 1.1, you have to use an object-based stack, meaning that the internal data type used in the
stack is an amorphous object, and the stack methods interact with objects:
public class stack
{
object[] m_items;
public void push(object item)
{...}
public object pop()
{...}
}
code block 1 shows the full implementation of the object-based stack. because object is the
canonical .net base type, you can use the object-based stack to hold any type of items, such as integers:
stack stack = new stack();
stack.push(1);
stack.push(2);
int number = (int)stack.pop();
code block 1. an object-based stack
public class stack
{
readonly int m_size;
int m_stackpointer = 0;
object[] m_items;
public stack():this(100)
{}
public stack(int size)
{
m_size = size;
m_items = new object[m_size];
}
public void push(object item)
{
if(m_stackpointer >= m_size)
throw new stackoverflowexception();
m_items[m_stackpointer] = item;
m_stackpointer++;
}
public object pop()
{
m_stackpointer--;
if(m_stackpointer >= 0)
{
return m_items[m_stackpointer];
}
else
{
m_stackpointer = 0;
throw new invalidoperationexception("cannot pop an empty
stack");
}
}
}
however, there are two problems with object-based solutions. the first issue is performance. when
using value types, you have to box them in order to push and store them, and unbox the value types
when popping them off the stack. boxing and unboxing incurs a significant performance penalty in their
own right, but it also increases the pressure on the managed heap, resulting in more garbage collections,
which is not great for performance either. even when using reference types instead of value types, there
is still a performance penalty because you have to cast from an object to the actual type you interact with
and incur the casting cost:
stack stack = new stack();
stack.push("1");
string number = (string)stack.pop();
the second (and often more severe) problem with the object-based solution is type safety. because the
compiler lets you cast anything to and from object, you lose compile-time type safety. for example, the
following code compiles fine, but raises an invalid cast exception at run time:
stack stack = new stack();
stack.push(1);
//this compiles, but is not type safe, and will throw an exception:
string number = (string)stack.pop();
you can overcome these two problems by providing a type-specific (and hence, type-safe) performant
stack. for integers you can implement and use the intstack:
public class intstack
{
int[] m_items;
public void push(int item){...}
public int pop(){...}
}
intstack stack = new intstack();
stack.push(1);
int number = stack.pop();
for strings you would implement the stringstack: