This is the second in a serious of articles I'm writing on the new features in the .NET framework V2.0. Today's topic is generics.
Generics is a mechanism used to parameterize types(classes,interface etc.) with other types. The best way to explain this is with an example:
public class Jar<T>
{
private T _member;
public T member
{
get
{
return _member;
}
set
{
_member = value;
}
}
}
The new construct introduced is the <T> directly after the class name, called a "type parameter". T is just a variable name, for some reason when using generics, people tend to go for just one capital letter instead of a multi-letter name. Once this name is declared, we can use it inside the class definition exactly as we would any other type such as int,string,Arraylist,Dataset etc. Notice though that T is not linked to any specific type, yet. This is where the advantage of generics comes in, we can replace T with any type when we create an instance of Jar, like so:
Jar<int> myJar = new Jar<int>();
or
Jar<ArrayList> myJar = new Jar<ArrayList>();
a major advantage of this approach is that types will be checked at compile time, for example if we use <int>, the following statement will result in a compile time error:
myJar.member = "ABC";
because the types are fixed at compile time, we do not have to do upcasts and downcasts like we would have to do without generics. This is demonstrated in collection classes such as ArrayList (data structure classes are the classical use of generics and are implemented as part of the .NET framework 2.0 under the System.Collections.Generic namespace):
//OLD WAY /////////////////////////////////////////////
ArrayList oldWay = new ArrayList
();
for (int x = 0; x < 10; x++)
{
//the add method upcasts from int to object
oldWay.Add(x);
}
int sum = 0;
for (int x = 0; x < 10; x++)
{
//have to downcast from object to int (unsafe)
sum += (int)oldWay[x];
}
//NEW WAY /////////////////////////////////////////////
List<int> newWay = new List<int>();
for (int x = 0; x < 10; x++)
{
//no upcast needed
newWay.Add(x);
}
sum = 0;
for (int x = 0; x < 10; x++)
{
//no downcast needed
sum += newWay[x];
}
Another advantage of generics is the loose coupling achieved between Jar and T.
A nice feature that Microsoft built in is the ability to specify constraints on type parameters. This is acomplished using another new keyword, "where". For example, if I wanted to write a generic factory class, I could do this:
public class Factory<T> where T : new()
{
public T getInstance()
{
return new T();
}
}
The where clause(not to be confused with a SQL where clause :) ) above indicates that whatever type I pass in for T must have a public parameterless constructor, if it doesn't I'll get a compile time error. The other constraints I can use is :
where T : struct = T must be a value type
where T : class = T must be a reference type
And the most useful one (in my opinion) :
where T : <class or interface name>
which will ensure that T inherits from a certain base class or implements a certain interface. We can of course give a comma sperated list of interfaces and a maximum of one base class. Once we've done this we can actually call methods on T like so:
public class myCollection<T> where T : IList
{
private T _myList;
public myCollection(T t)
{
_myList = t;
}
public void Add(object x)
{
_myList.Add(x);
}
}
Which will compile without error. You can also compose constraints by comma delimiting them:
public class myCollection<T> where T : new(),IList
You can use more than one type parameter by comma delimiting them in the declaration for example:
public class myCollection<T,P,X>
Type parameters are not limited to classes only, you can specify them on method level as well, like so:
public class myFactory
{
public T createInstance<T>() where T:new()
{
return new T();
}
}
(not very useful but good as an example).
As someone famous once said: "That's all I have to say about that!" :) There's a bit more to generics that you can read up on in the help files, but this should give you a pretty good idea of what it's about.