Introduction
This article uses a simple list scenario to illustrate the importance of Generics.
Background
C# 2.0 is a powerful language with some interesting features, including partial classes, nullable types, anonymous methods, generics, and so many others.
Of all these features, I find Generics interesting, especially when dealing with lists or a collection of Items.
In this article, I use a simple list scenario to show the importance of Generics.
Simple Definition
Generics are code templates that allow developers to create type-safe code without referring to specific data types.
In simple terms, Generics are classes, structures, interfaces and methods that have placeholders for one or more of the types they store or use.
Simple List Scenario
Consider a scenario where you have two lists of values i.e. list1
and list2
, where list1
contains integer values and list2
contains string
s as shown below:
List1 = {1,2,3,4}
List2 = {"string1","string2","string3"}
Now assume you want to develop a class which can be used to store either of the list items (list1
items or list2
items).
Since both integers and string
s can be treated as "objects" in C#, you decide that your class will store an array of objects, so you come up with the class in code below.
Class Used as a Collection of Objects
public class MyObjCollection
{
object[] items = new object[50];
int numitems = 0;
public void Add(object item)
{
if (numitems + 1 < 50)
{
items[numitems] = item;
numitems++;
}
}
public object this[int n]
{
get { return items[n]; }
}
}
Now to store the list1
items (integers) using this class, we use the code below:
MyObjCollection objintcollection = new MyObjCollection();
objintcollection.Add(1);
objintcollection.Add(2);
objintcollection.Add(3);
objintcollection.Add(4);
Note: Since the Add(object item)
method expects an object, we can also add other datatypes to this collection other than integers. For example:
objintcollection.Add("string 1");
Now if we wanted to store list2
items, we would have similar code, only that we name our instance differently:
MyObjCollection objstringcollection = new MyObjCollection();
objstringcollection.Add("string1");
objstringcollection.Add("string2");
objstringcollection.Add("string3");
Still the system would allow us to add non string
values to this collection, e.g.
objstringcollection.Add(3);
Type-Safety
The fact that we can add non integer values to the collection storing list1
values and we can also add non string
values to the collection storing the list2
values, shows that our collections are not type-safe.
Type Casting
To retrieve a value from any of these collections, type casting has to be done, e.g.
Int value1 = (int)objintcollection[0];
Or:
string str1 = (string)objstringcollection[0];
This is a problem, because type casting reduces performance.
InvalidCastExceptions
The C# compiler will always compile even if you are casting an integer value to a string
or vice versa, however you will get an exception at run time. For example, the code below would compile:
string value1 = (string)objintcollection[0];
However remember that you stored integers in the collection, so at runtime you will get an InvalidCastException
. To realise the importance of Generics while dealing with lists, let's consider a Generic class, which stores an array of Generic types as opposed to an array of objects, in code below.
Generic Class stores a collection of values of a given datatype.
public class MyGenCollection<T>
{
T[] items = new T[50];
int numitems = 0;
public void Add(T item)
{
if (numitems + 1 < 50)
{
items[numitems] = item;
numitems++;
}
}
public T this[int n]
{
get { return items[n]; }
}
}
Note: This class is similar to the object collection class, but instead of having a collection of objects, we have a collection of the Generic type T
. In the declaration of the class, i.e. public class MyGenCollection<T>
, <T>
is a place holder, and T
is a type argument to be replaced by any datatype. Assuming you want to use the above class to store the list1
items (integers), we would use the code below:
MyGenCollection<int> intcollection = new MyGenCollection<int>();
intcollection.Add(1);
intcollection.Add(2);
intcollection.Add(3);
intcollection.Add(4);
Note: The Add(int item)
method in this case only accepts values of type int
, so adding any other datatype will cause a compile time error. For example, the code below will not compile:
intcollection.Add("string 1");
To store the list2
items(string
s), we would instantiate the class as below:
MyGenCollection<string> stringcollection = new MyGenCollection<string>();
stringcollection.Add("string1");
stringcollection.Add("string2");
stringcollection.Add("string3");
This makes the Generic collection class type safe, considering you specify to the system the datatype of the values you want to store in the collection.
Type Casting Not Needed
Also note that you don't need to type cast any values retrieved from the collection. For example, the code below is enough to retrieve the first item of the integer collection:
int value1 = intcollection[0];
No InvalidCastExceptions
Since there are no datatype casts being done, the Invalid Cast Exceptions are eliminated.
Generic Methods
Methods can also be defined with Generic type parameters, for example:
public void doAction<T>(T argument)
{}
You can also have:
public T doAction<T>(T argument)
{}
T
is the type argument and can be specified when the method is being called, for example:
Myobject.doAction<int>(56);
Note: Since type T
is generic, some datatype specific operations may not be possible within the method body, for example the code below wouldn't compile:
public T GetSum<T>(T arg1, T arg2)
{
return arg1+arg2;
}
However with Inheritance, a Generic abstract
method can be declared and a datatype passed at implementation of the method, for example:
public abstract class MyClassA<T>
{
public abstract T GetDifference (T arg1, T arg2);
}
public class MyClassB: MyClassA<int>
{
public int GetDifference(int arg1, int arg2)
{
return arg1 - arg2;
}
}
Conclusion
Generics are handy especially when you wish to develop code templates that can be used with any datatype at any given moment. Generics are also applicable in other concepts of programming including inheritance, for example Generic Interfaces, Reflection, Remoting, Delegates, etc. It is important that developers master the working of Generics, to develop advanced solutions that are manageable, scalable and with good performance.
Phillip Walera
Sys Analyst/Developer
Founder Sharepoint Africa
History
- 24th April, 2007: Initial post