code{color:#833;background:#fcfcfc;}
h4{margin:30px 0px 0px 0px;font-color:#fff;font-weight:bold;border-bottom:1px dashed #ccc;font-variant: small-caps}
Introduction
There are language features that are nothing more than syntactical sugar. For example, C#’s coalesce operator (??) is a short-handed and specialized if-else. Object initializers make it easier to set properties on a newly created objects. Some features though go beyond mere convenience and add real value. I know it seems like we constantly have to learn new things, while at the same time actually produce code to pay our bills. It can be hard to pick and choose what to learn and what can wait. Let me be straight up though: if you haven’t mastered generics yet, you’re starting to fall dangerously behind.
I don’t say that to put you down. I say it because not only are they used all over the place (and it’s important that you understand the code that you use), but also because they’ve been around for a relatively long while and provide some serious benefits (generics were first introduced in .NET 3 years ago, but the concept dates back to the 70s).
I think the problem is threefold:
- The syntax is odd,
- The problem generics solve isn’t well understood, and
- How they actually work isn’t clear
The Problem
The best way to learn how generics work is by looking at the problem they solve – which is rather straightforward. You use a generic whenever you have a piece of code which can be re-used by different types. The classic example is a data structure, like a list or a hashtable.
A list is a list regardless of the type of object being stored within it. Whether we’re adding a string
or an int
, or removing a User
or a Textbox
, the underlying code is the same. Without generics, we have two choices if we want to create a list that supports a wide range of types. The first is to use a list of System.Object
(such as the built-in System.Collections.ArrayList
). But this has two serious drawbacks. The first is one of type-safety:
var list = new ArrayList();
list.Add(“some name”);
int id = (int)list[0];
The above code will compile just fine, but it will cause a runtime error. The other is performance. The only way for .NET to cast a value-type (int
, bool
, long
, float
, …) to an object is to box and unbox the value (a technique used to transfer a value from the stack to the heap).
The other solution is to create an explicit list for each type that we want to support. This was so common that a CollectionBase
class is included within the .NET framework for just this purpose. This approach performs well and is strongly-typed, but it requires a huge amount of repetitive code.
The Solution
We can use generics to solve the above-mentioned problems – the only real downside is that they can be intimidating. Let’s build a very simple generic list:
public class SimpleList<T>
{
private T[] _innerList = new T[10];
private int _count;public void Add(T item)
{
if (_count+1 == _innerList.Length)
{
GrowOurList();
}
_innerList[_count++];
}
public T Get(int index)
{
return _innerList[index];
}
private void GrowOurList(){… do something …}
}
This is a simple generic class that can efficiently and safely support any type. Don’t worry about what GrowOurList
does – a common growth algorithm is to simply create a new container that’s twice the size of the original and copy the old array into the newly created one. But what’s all this code really mean?
The magic happens around the class definition that defines the generic, <T>
. T
is an arbitrary name that I’ve given to our generic – some prefer to use more meaningful names, like <ListItemType>
. T
is a placeholder for any type. Within this class wherever you see T
you should think of it as a yet-to-be defined type. So, when we create an instance of our list like:
var list = new SimpleList<int>();
The .NET runtime engine will replace every T
with int
, so our class actually becomes (just the relevant outline provided) :
public class SimpleList<int>
{
private int[] _innerList = new int[10];public void Add(int item){…}
public int Get(int index){…}
}
If instead we create a list of strings, then T
gets replaced with string
. Notice that the containing array is actually turned into an array of integers – no boxing and unboxing. Also, notice that the parameter for Add
is actually an int
, as is the return type for Get
– no unsafe casting.
Generic Repository
Hopefully the above example helps clarify what generics are and how they work. It isn’t a very realistic example though – you’ll likely never have to create such a list. But generics really can come in handy in a number of real situations. Say that you’re doing domain driven design and have a number of domain classes. You decide to have a repository for each domain (or maybe you decide to skip repositories and hit a DAL directly, or maybe you decide to put repository-like code directly in your domain, it doesn’t matter), you’ll likely end up with something like:
public class UserRepository
{
public User GetUser(int id) { … }]
public void Delete(User user) {….}
public void Save(User user) { … }
}public class OrderRepository
{
public User GetOrder(int id) { … }]
public void Delete(Order order) {….}
public void Save(Order order) { … }
}public class SupplierRepository
{
public User GetSupplier(int id) { … }]
public void Delete(Supplier supplier) {….}
public void Save(Supplier supplier) { … }
}
(of course you’ll have more methods, which won’t necessarily be shared by all repositories). This is an ideal candidate for a repository – similar code where the type is the main difference.
public class Repository<T>
{
public T Get(int id) { … }
public void Delete(T item) { … }
public void Save(T item) { … }
}
If you do need a specific non-generic method, you can easily do something like:
public class UserRepository : Repository<User>
{
public User GetUser(string username, string password) { … }
}
Which’ll make the generic repository methods available to your UserRepository
, in addition to the specific GetUser
implementation.
I don’t want to get too far off topic, but you might be wondering how I’d actually implement our generic Get
or Delete
or Save
method. My real answer is that you’d use NHibernate which itself relies on generics, so eventually Get<T>(int id)
would call NHibernate’s own ISession.Get<T>(int id)
method. Without getting into NHibernate though, a naïve (and foolish) implementation might look something like:
/* don’t actually try to build it this way! */
var sql = string.Format(“SELECT * FROM [{0}] where id = @id”, typeof(T).Name);
You can even take this a step further (and you probably should) and use a generic query object which would let you implement generic Find
, FindAll
, FindOne
, Exists
and Count
methods:
var query = new ModelQuery<User>.Where(w => w.Criteria(“name”, Operation.Equals, “crush”).AtPage(2).LimitedTo(20);
var users = new Repository<User>().Find(query);
Multiple Generics
The syntax really gets confusing when you introduce multiple generics, such as the popular Dictionary<T, K>
. There’s really nothing special here beyond what we’ve already covered. Instead of having 1 type placeholder, we have two. So given a method that looks like:
public void Add(T key, K value){… }
and code that instantiates our dictionary that looks like:
var dictionary = new Dictionary<int, User>();
the runtime engine actually turns our Add
method into:
public void Add(int key, User value) { … }
It’s really that simple.
Constraints
All the examples we’ve looked at so far can support any type. Sometimes though you might want to limit the type that <T>
can be replaced with. For example, in the case of our Repository, it doesn’t make sense to use an int
. For such cases we can apply constraints to our generics:
public class Repository<T> where T : class { … }
If we now try to create a Repository of type Int
, we’ll get a compiler error. In addition to being able to constrain our generics to any interface or object, we can also constrain our generics to any reference type (as above) via the class
keyword, or any value types via the struct
keywords. The most interesting constraint is achieved via the new()
keyword:
public class Something<T> where T : new() { … }
This means that the type must have a public-parameterless constructor. This is a special constraint that allows us to create an instance of the type T
. The simplest example of where you’d want to use this is for a generic instance factory (which you probably wouldn’t need in day-to-day usage, but you might use tools, like mocking frameworks or DI frameworks that do):
public class GodOf<T> where T : new()
{
public T Create()
{
return new T();
}
}
Which can then be used via:
var instance = new GodOf<User>().Create();
Generic Methods
Generics don’t have to exist at the class level. They can actually exist at the method level. Our above class is probably a good candidate for this. We can change or GodOf
class to:
public class God
{
public static T Create<T>() where T : new()
{
return new T();
}
}
With the following usage:
var instance = God.Create<User>();
Conclusion
Admittedly we looked at some pretty wild examples – a list that you’ll never create, an advanced generic repository with generic query objects, and an instance factory. Our goal though was to focus on understanding the problem generics solve, as well as zeroing in on that crazy syntax.
You do (or should) deal with generics on a daily basis – at the very least the generic collections built into .NET as of the 2.0 release. Once your understand of generics grow, not only can you use them in more advanced scenarios (like the built-in generic delegates), but also add them to your own code.
good job,nice post!
another awesome article karl, nice one.
i take it on your repository implementations without generics, the GetXXX methods were not all supposed to return User objects right? 😉
Good catch Shawn.
I think line 12 of the second code block should be:
_innerList[_count++] = item;
I haven’t understood generics until just now! Thanks for the helpful post.
Very helpful thanks. Perfect for me as I am just getting trying to get to grips with generics at the moment at work.
ya, .NET 2.0, 3 years ago
“(generics were first introduced in .NET 3 years ago, but the concept dates back to the 70s)”
I’d say you have a typo. Generics were introduced in .NET 2.0, not 3.0.
Very well written Karl! This definitely deserves a place in your Foundations of Programming book. 😉
Thanks for a great post!
Even if you use generics on a daily basis it’s always good to go back to basics and contemplate what youre actually doing every once in a while.
Thank you for a very nice writeup. I like that you started out with “What problem does this solve?” That’s the key to understanding.
Nice post. Thanks for the info.
Nice introduction. I will remember to bookmark this one so I have something to throw at the people who still refuse to learn generics.
I finished my implementation of Repository for Linq2Sql yesterday.
It makes bootstrapping new Linq2Sql projects that much easier.
That people still rely on ArrayList etc. is just crazy.