November 15th, 2008
Generics in C# - 0

You heard might have heard about generics, but how do they work in C# ? And what is that <T> doing in the class definition? This article shows you the benefits of using generics, and how to implement them yourself.
Version 2 of C# introduced something called a “generic”. In this article we will look into what a generic is, and why you would want to use them. Generics are similar to a concept frequently used in C++ Templates. They allow you to better re-use your code while at the same time providing more information to the compiler on what should be allowed, and what shouldn’t.
The code below give an example on how generics can improve the compilers knowledge of what it should accept. A very basic vector array class is defined to accept only strings.
This code is just for illustration of the Generics concept. Normally you would just use the build in C# Array type to obtain the same Array functionality.
class Vector
{
string[] elements;
public Vector(int maximum_elements)
{
elements = new string[maximum_elements];
}
public string this[int i]
{
get {return elements[i];}
set {elements[i] = value;}
}
}
class MainClass
{
public static void Main(string[] args)
{
Vector test = new Vector(20);
test[1] = "42";
// The compiler catches this, we cannot convert an integer to string
// test[2] = 123;
string theAnswer = test[1];
Console.WriteLine("The answer is {0}" , theAnswer );
}
}
The above vector array code can only be used for storing strings,limiting our re-use of the code. What to do if we would like to store integers instead?
We can improve the code by making it more abstract by replacing the int definitions with the more general object tag.
It is now possible to store all kinds of elements (not just integers) in the Vector. But because the compiler no longer knows what type is stored in the Vector, it allows all kinds. Worse, we now need to cast all return values to our main type (string) as the compiler cannot implicitly convert objects to strings.
class Vector
{
object[] elements;
public Vector(int maximum_elements)
{
elements = new object[maximum_elements];
}
public object this[int i]
{
get {return elements[i];}
set {elements[i] = value;}
}
}
class MainClass
{
public static void Main(string[] args)
{
Vector test = new Vector(20);
test[1] = "42"; // The Vector doesn't know about our type
test[2] = 123;// So both strings and numbers are OK, allowing for mistakes
string theAnswer = (string) test[1];// We are also forced to cast (object) to (string)
Console.WriteLine("The answer is {0}" , theAnswer );
}
}
Generics help out by allowing us to define our Vector as an abstract type. Note the <T> given in the class definition of the Vector. It replaces all instances where we would have said “string”, “int” or “object” previously.
class Vector<T>
{
T[] elements;
public Vector(int maximum_elements)
{
elements = new T[maximum_elements];
}
public T this[int i]
{
get {return elements[i];}
set {elements[i] = value;}
}
}
class MainClass
{
public static void Main(string[] args)
{
Vector<string> test= new Vector<string>(20);
test[1] = "42";
// The compiler catches this, we cannot convert an integer to string
// test[2] = 123;
string theAnswer = test[1];
Console.WriteLine("The answer is {0}" , theAnswer );
}
}
The above example removes the need to typecast any returned value, the compiler is already aware of the correct type (”string” in our example). The code is also type-safe as it is no longer possible to assign the wrong type to an element in the Vector and finally, the above Vector code can be re-used to hold any kind of variable.
We could re-use the final code to create any kind of container; suitable for holding only a single type.
Vector<int> test1 = new Vector<int>(20); Vector<string> test2 = new Vector<string>(20); Vector<float> test3 = new Vector<float>(20);
Image credit: bucklava









Except where otherwise noted, content on this site is