AddThis Social Bookmark Button

Print

C# Generics

by Jesse Liberty
05/17/2004

The single most anticipated (and dreaded?) feature of Visual C# 2.0 is the addition of Generics. This article will show you what problems generics solve, how to use them to improve your code, and why you need not fear them.

What Are Generics?

Many people find Generics confusing. I believe this is because they are usually explained and demonstrated before the reader understands what problem it is they were designed to solve. You end up with a solution in search of a problem.

This column will try to stand that process on its head and begin with a simple question: what are generics for? The short answer is this: without generics, it is very difficult to create type-safe collections.

C# is a type-safe language. Type safety allows potential errors to be trapped by the compiler (reliably), rather than to be found at run time (unreliably, and often only after you've shipped your product!). Thus, in C#, every variable has a defined type; when you assign an object to that variable, the compiler checks to see if the assignment is valid and notifies you if there is a problem.

In version 1.1 (2003) of .NET, this type safety falls apart when you use collections. All of the collection classes provided by the .NET library are typed to hold objects, and since everything derives from the base class Object, every type can be put into the collection. There is, therefore, effectively no type checking at all.

Related Reading

Programming C#
By Jesse Liberty

Worse, each time you take an object out of a collection you must cast it to the correct type, which incurs a performance hit, and makes for ugly code (and if you mis-cast, throws an exception). Further, if you add a value type (e.g., an integer) to the collection, the integer is implicitly boxed (another performance penalty), and explicitly unboxed when you take it out of the collection (yet another performance penalty and more casting).

For more on boxing, please see "Trap #4, Watch Out For Implicit Boxing."

To see these problems in action, we're going to create about as simple a linked list as you can imagine (all of the bells and whistles will be left as an exercise for the reader). For those of you who have never created a linked list, the concept is quite simple. Imagine a chain of boxes; each box (called a Node) contains a bit of data and also a reference to the next box in the chain (except for the last box, which sets its reference to the "next box" to null).

To create our simplified linked list, we'll need three classes:

  1. The Node class, to contain the data and the reference to the next Node
  2. The LinkedList class, to contain the first Node in the list and any additional information about the list
  3. The test application, to exercise the LinkedList class

To see how the linked list works, we'll add two types of objects to the list: integers and Employees. You can imagine that an Employee class might contain all of the information needed about each employee in a company. For our purposes, the Employee object can be painfully simple.


public class Employee
{
  private string name;
  public Employee (string name)
  {
    this.name = name;
  }
  public override string ToString()
  {
    return this.name;
  }
}

The class consists of nothing but a private string holding the Employee's name, a constructor to set that value, and an override of the ToString() method that returns the value of the Employee's name.

The linked list itself consists of Nodes. The Node, as noted above, must hold the data (integer or Employee) and a reference to the next Node in the list.


public class Node
{
  Object data;
  Node next;
  public Node(Object data)
  {
    this.data = data;
    this.next = null;
  }
  public Object Data
  { 
    get { return this.data; }
    set { data = value; }
  }
  public Node Next
  {
    get { return this.next; }
    set { this.next = value; }
  }    

Note that the constructor sets the private data member to the object that is passed in, and that the next property is initialized to null.

The class also includes a method, Append, that takes as an argument a new Node, which we'll append to the end of the list. This is accomplished by asking the current Node to see if its next property is null. If so, the current node is the last node in the list; we set that property to point to the new Node and thus have appended the new node to the end of the list.

If the current Node's next property is not null, then we're not yet at the end of the list. Since the next property is a Node, we call Append on that node, passing in the new Node we were given, thus passing the new Node down the list in search of the end. Eventually it will find the end, and be appended there.


public void Append(Node newNode)
{
  if ( this.next == null )
  {
    this.next = newNode;
  }
  else
  {
    next.Append(newNode);
  }
}

The ToString method is overridden in Node, both to return the value of the data, and also to tell the next Node in the list to return the value of its data.


public override string ToString()
{
  string output = data.ToString();
  if ( next != null )
  {
    output += ", " + next.ToString();
  }
  return output;
}

Thus, when you invoke ToString() on the head Node, you see the value of all of the data in the list.

The LinkedList class itself holds a reference to a single Node, the headNode, initialized to null.


public class LinkedList
{
  Node headNode = null;

Pages: 1, 2

Next Pagearrow