How to Use C# Generics
As with prescriptions at your local pharmacy, you can save big by opting for the generic version. Generics, introduced in C# 2.0, are fill-in-the-blanks classes, methods, interfaces, and delegates. For example, the
List<T> class defines a generic array-like list that’s quite comparable to the older, nongeneric
ArrayList — but better! When you pull
List<T> off the shelf to instantiate your own list of, say,
ints, you replace
List<int> myList = new List<int>(); // A list limited to ints
The versatile part is that you can instantiate
List<T> for any single data type (
CorduroyPants — whatever), and it’s still type-safe like an array, without nongeneric costs. It’s the superarray.
Generics come in two flavors in C#: the built-in generics, such as
List<T>, and a variety of roll-your-own items.
What’s so hot about generics? They excel for two reasons: safety and performance.
Generics are type-safe
When you declare an array, you must specify the exact type of data it can hold. If you specify
int, the array can’t hold anything other than
ints or other numeric types that C# can convert implicitly to
int. You see compiler errors at build-time if you try to put the wrong kind of data into an array. Thus the compiler enforces type-safety, enabling you to fix a problem before it ever gets out the door.
A compiler error beats the heck out of a runtime error. Compiler errors are useful because they help you spot problems now.
The old-fashioned nongeneric collections aren’t type-safe. In C#, everything IS_A
Object is the base type for all other types, both value-types and reference-types. But when you store value-types (numbers,
structs) in a collection, they must be boxed going in and unboxed coming back out. It’s as though you’re putting items in an egg carton and having to stuff them inside the eggs so that they fit and then breaking the eggshells to get the items back out. (Reference-types such as
BankAccount don’t undergo boxing.)
The first consequence of nongenerics lacking type-safety is that you need a cast, as shown in the following code, to get the original object out of the
ArrayList because it’s hidden inside an
ArrayList aList = new ArrayList();
// Add five or six items, then ...
string myString = (string)aList; // Cast to string.
Fine, but the second consequence is this: You can put eggs in the carton, sure. But you can also add marbles, rocks, diamonds, fudge — you name it. An
ArrayList can hold many different types of objects at the same time. So it’s legal to write this:
ArrayList aList = new ArrayList();
aList.Add(“a string”); // string -- OK
aList.Add(3); // int -- OK
aList.Add(aStudent); // Student -- OK
However, if you put a mixture of incompatible types into an
ArrayList (or another nongeneric collection), how do you know what type is in, say,
aList? If it’s a
Student and you try to cast it to
string, you get a runtime error. It’s just like Harry Potter reaching into a box of Bertie Botts’s Every Flavor Beans: He doesn’t know whether he’ll grab raspberry beans or earwax.
To be safe, you have to resort to using the
is operator or the alternative, the
// See if the object is the right type, then cast it ...
if(aList[i] is Student) // Is the object there a Student?
Student aStudent = (Student)aList[i]; // Yes, so it’s safe to cast.
// Or do the conversion and see if it went well...
Student aStudent = aList[i] as Student; // Extract a Student, if present;
if(aStudent != null) // if not, “as” returns null.
// OK to use aStudent; “as” operator worked.
You can avoid all this extra work by using generics. Generic collections work like arrays: You specify the one and only type they can hold when you declare them.
Generics are efficient
Polymorphism allows the type
Object to hold any other type, as with the egg carton analogy. But you can incur a penalty by putting in value-type objects — numeric,
bool types and
structs — and taking them out. That’s because value-type objects that you add have to be boxed.
Boxing isn’t worrisome unless your collection is big (although the amount of boxing going on can startle you and be more costly than you imagined). If you’re stuffing a thousand, or a million,
ints into a nongeneric collection, it takes about 20 times as long, plus extra space on the memory heap, where reference-type objects are stored. Boxing can also lead to subtle errors that will have you tearing your hair out. Generic collections eliminate boxing and unboxing.