How to Use C# Generics - dummies

By John Paul Mueller, Bill Sempf, Chuck Sphar

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 T with int:

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 (string, Student, BankAccount, 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 because Object is the base type for all other types, both value-types and reference-types. But when you store value-types (numbers, chars, bools, and 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 string, Student, and 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 Object:

ArrayList aList = new ArrayList();

// Add five or six items, then ...

string myString = (string)aList[4]; // 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[3]? 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 as operator:

// 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, char, and 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.