What the heck is Generics?


The general perception is 'Generics' is a dreadful monster! In my opinion it is thus because most places that I've read upon has explained and some even demonstrated before really making it clear for why the heck we even want to use this dreadful creature and in what scenarios.

Generics are the most powerful feature of C# 2.0

C#, unlike C++, is a true type-safe language. Type safety allows potential errors to be trapped early on, right at the compilation time. 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.

Under .NET Framework 1.1, 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.

Attacking two problems at single Go:

  • Problem 1: No type-safety (Program Reliability)

Because the compiler lets you cast anything to and from Object, you lose compile-time type safety.

  • Problem 2: Boxing & Un-boxing (Performance Hit)

Furthermore, if you add a value type (e.g., an integer) to the collection, the integer is implicitly boxed (at a cost of performance), and explicitly unboxed when you take it out of the collection (yet another performance penalty and more casting).

Let's take a look at the illustration code as put up in Listing 1

Class List is a simple Collection class, that stack-up elements of type <Object> (in other words, it accepts technically any data-typed element, as all data types are inherited from the base class <Object>, any way).

Listing 1

Public
class List
{
private object[] elements;
private int count;
public void Add(object element)
{
if (count == elements.Length) Resize(count * 2);
elements[count++] = element;
}
public object this[int index]
{
get { return elements[index]; }
set { elements[index] = value; }
}
public int Count
{
get { return count; }
}
}

As illustrated in Listing 2, technically we can add an element of <integer> as well as <String> data type (in other words, it would be a list of bunch of apples and oranges, together). More so, as mentioned earlier, an explicit cast is required to retrieve element from the collection.

Listing 2

List intList = new List();
intList.Add(1);
// Argument is boxed
intList.Add(2); // Argument is boxed
intList.Add("Three"); // Should be an error (but it is NOT!)
int i = (int)intList[0]; // Cast required

Solution:

Now, one of the solutions is perhaps to derive a specific data-typed Collection from class List, say ListOfInt as illustrated below, in Listing 3

Listing 3

Public class ListOfInt
{
private int[] elements;
private int count;
public void Add(int element)
{
if (count == elements.Length) Resize(count * 2);
elements[count++] = element;
}
public object this[int index]
{
get { return elements[index]; }
set { elements[index] = value; }
}
public int Count
{
get { return count; }
}
}

Unfortunately, solving the performance and type-safety problems this way introduces a third, and just as serious problem-productivity impact. Writing type-specific data structures are a tedious, repetitive, and error-prone task. When fixing a defect in the data structure, you have to fix it not just in one place, but in as many places as there are type-specific duplicates of what is essentially the same data structure. In addition, there is no way to foresee the use of unknown or yet-undefined future types, so you have to keep an Object-based data structure as well.

The solution to the above mentioned problems is my favorite little monster 'Generics'. Listing 4 below shows the code modification to turn the code as put up in Listing 1, with an objective of creating a type-safe, generic Collection class. (and if you are wondering why 'Generics' is called-so, I am sure, you reckon it by now)