Code Climber: A Touch of Generics in the Morning

This article is meant as a gentle introduction to generics. It will be the first in a series of writings and multimedia files I’ll produce on this subject.

Since this is the first technical blog I’ve written for Microsoft, I’ll say a few words by way of introduction. I want to write two types of technical articles.  One type will cover existing technologies, such as generics, and other articles will focus on new technologies, such as LINQ. I feel that both types of articles need to be written, but that it is particularly important that existing products are covered here on Microsoft’s web sites.

As a technical writer, I usually aim for the general reader. There are many other excellent bloggers here at Microsoft who cover advanced topics. My primary goal is to tackle major programming topics and cover them in a simple, straightforward manner that is easy for programmers to digest. Occasionally I’ll take forays into fun topics that interest me, or tackle advanced topics that are particularly important. But the majority of my efforts are aimed at communicating as clearly as possible about major topics of interest to most C# developers.

Why Use Generics

Generics help developers write type safe code that is easy to reuse. They help catch errors at compile time, rather than at run time. Generics make it easier for developers to write code that is self descriptive. In particular, they can help you avoid the confusing syntax and side effects involved in type casting and boxing. When writing containers, they can also help you write one class for use with many different types, rather than forcing you to write a new class for each type that you want to handle.

Early versions of Basic did not force you to declare the type of the variable. As a result, it may have seemed like an imposition to encounter a language such as Pascal or C# that is strongly typed. But once a developer understands types and how to use them, they learn that type safety is not an impediment to writing good code, but rather an aid. The same is true of generics. They are designed to help you write code that is easy to understand, and safe to use, and easy to reuse.

Of course, C# is already a type safe language. So why does it need generics? The answer is that generics help C# to be even more type safe than it was before. You can therefore file this whole issue under the following heading: "If a lot of type safety is a good thing, then more type safety is an even better thing."

This problem with using old fashioned C# code that lacks type safety can be illustrated by thinking about the standard C# ArrayList class. Consider this code:

1: ArrayList myList = new ArrayList();

   2: myList.Add("A String"); // Add a string

   3: myList.Add(100); // Add an Integer

   4: myList.Add(1.00); // Add a float

Here three different types are placed in a single collection. In particular, we add a String, an Integer, and a floating point type to the same collection.

Seen from one point of view, this degree of flexibility is very liberating. Seen from another point of view, this is a form of chaos. For instance, retrieving data from myList in a type save way depends on indexing into the collection in a particular order. Trouble could raise its head if you accidentally try to convert the String added to the collection into a variable of type Decimal. You could, however, convert either the Integer or the floating point number into a Decimal. It's all a matter of luck, which is great when playing a game of chance, but not so good when writing code.

One could go on about these kinds of problems at some length, including discussions of the need to typecast the data that you retrieve from the collection:

1: Decimal myDecimal = (Decimal)myList[2];

But there is no need to get into any more depth. It should already be clear that the ArrayList class has advantages in terms of flexibility, and disadvantages in terms if code safety.

Programmers who work under time constraints very much want their code to work the first time. Most developers are quite willing to give up a little flexibility in order to buy a bit of security.

In saying that generics provide a trade off between safety and flexibility, I do not mean to imply that the syntax or the implementation of generics is any way clunky or inflexible. As you read on, you will find that the syntax is quite malleable, and offers the developer a considerable degree of freedom to craft clever solutions to difficult problems. In many cases, using generics can help you improve your code’s performance.

To sum up, the main purpose of generics is to put typesafe constraints on the code that you write. Or, to state the same thing from the opposite point of view: the object of generics is to allow you to write one class that can be safely used with a wide variety of types without sacrificing type safety. The rest of this article will focus first on showing you how to use a single class with a variety of types, and then how to write a class that can be used with a variety of types.

Generics and Type Safety

It is now time to see an example of how generics offer type safety to developers. Consider the following simple example, which shows how generics provide you with a degree of type safety not available with standard C# collection types such as an ArrayList:

1: List<string> list = new List<string>();

   2: list.Add("Tom"); // This works because List is of type String

   3: list.Add(100); // Invalid argument error at compile time

   4: list.Add(1.0); // Invalid argument error at compile time

   5: String myString = list[0];

The first line of code in this example declares a generic List that is type safe. In particular, this list works with the string type, which is declared in angle brackets. As a result, you can only add strings to this container. Attempts to add Integers or Floats will result in a compile time error. Having this type of error occur at compile time, rather than potentially encountering such an error at run time, is one of the big strengths of generics.

In terms of functionality, the generic List class is roughly equivalent to the ArrayList type. It is meant to play the same role in generic programming that the ArrayList class plays when developing with standard containers. Like the ArrayList class, the generic List class supports the ICollection, IEnumerable and IList interfaces. The main differences between the types are two fold:

  1. The generic List class allows you to declare the type of items that you are going to add into your collection, while the ArrayList class provides no comparable service. If you declare a List class to be of a certain type, then it is impossible to add anything but that one type to the collection. In this case, we specified that the List was to be of type string.
  2. There is no need to typecast the list members when you access them. All the items on the list are guaranteed to be of type string, so there is no need to typecast them when accessing the collection.

Here is an example of accessing a member of a generic collection without needing to typecast:

   1: String myString = list[0];

The key thing to note in the example provided at the beginning of this section is that both lines 3 and 4 result in errors at compile time. It is not possible to add an Integer, a float, or anything but a string to this particular instance of the List class. As a result, you never have to worry about this class returning anything but a string. Hence you can access a member of this collection without a typecast.

Passing Generics Around

So far the code you have seen has been as straight forward as possible so that you could focus on the theory behind generics. Now it is time to start looking a little deeper, and seeing some of the syntax involved with somewhat more complex examples of this type of programming.

Let's get started by showing some straight forward examples of how to pass a generic container to a method, and how to return an instance of a container from a method:

   1: public void AddSampleData(List<string> list)

   2: {

   3:   list.Add("Tom");

   4:   list.Add("Mike");

   5: }

   6:  

   7: public List<string> CreateSimpleList(Boolean addSampleData)

   8: {

   9:   List<string> list = new List<string>();

  10:   if (addSampleData)

  11:   {

  12:     AddSampleData(list);

  13:   }

  14:  

  15:   return list;

  16: }

As you can see, these examples are simple and straightforward. In the AddSampleData method, we pass in a List class of type string, and we add some sample data to it. After the method is called, the list will contain at least two strings. The CreateSimpleList method gives an example of how to call AddSampleData. Note that on line 9 an instance of the List class is created, and on line 12 the AddSampleData is called.

Here is code that shows how to call the CreateSimpleList method, and then display the list in a list box:

1: List<String> list = CreateSimpleList(true);

   2:  

   3: foreach (String item in list)

   4: {

   5:   listBox1.Items.Add(item);

   6: }

 

For programmers, life doesn’t get much simpler than this. The call to CreateSimpleList creates an initialized instance of the List class, and the foreach statement adds the members of the collection to a listbox.

Passing Generics Around Genericly

The code in the last section gives you an easy to understand example of how to pass a generic type to a method. The problem with the code, however, is that it is not very generic! You can, for instance, pass in a list of type string, but you can't generically pass in just any type, such as a list of Integers.

To see a solution to this problem, consider the following simple class:

1:     public class GenericMethod01

   2:     {

   3:         public void RunTest()

   4:         {

   5:             // Create sample data

   6:             int[] intData = { 0, 1, 2 };

   7:             String[] stringData = { "This", "is", "a", "series", "of", "words" };

   8:             List<String> myStrings = new List<string>(stringData);

   9:   List<int> myInts = new List<Int32>(intData);

  10:  

  11:             // Show the data

  12:             DisplayData<int>(myInts);

  13:             DisplayData<String>(myStrings);

  14:         }

  15:  

  16:         public void DisplayData<T>(List<T> values)

  17:         {

  18:             foreach (T value in values)

  19:             {

  20:                 Console.Write(value + " ");

  21:             }

  22:             Console.WriteLine();

  23:         }

  24:     }

The DisplayData method shown at line 16 is declared to take a generic List of type T. The T in this case is meant to be a placeholder for a generic type that can change at run time. If you look back at lines 12 and 13, you can see that we call the DisplayData method first with a List of Integers, and then with a list of Strings. DisplayData can handle both types. Notice, however, that we do in fact explicitly declare the type that will be passed in when we call the method:

 

  12:             DisplayData<int>(myInts);

  13:             DisplayData<String>(myStrings);

After the word DisplayData, we have an open bracket and then an explicit declaration of the type we are going to use. Once you get used to this syntax, you can see that there really is not much magic here at all. We are explicitly declaring the type to use, but the declaration is not quite in the place you might normally expect to find it. Note that this means that type T, when inside the DisplayData method, is type safe. If you pass in a List containing Integers, then T is of type int. If you run the code in the VS debugger, and wave your mouse over the variable T, then you will be able to see its type. Furthermore, you don’t have to use type casts when working with T.

Summary

We’ve now reached the point where things start to get, depending on your perspective, either more interesting, or more complicated.  There are all sorts of questions that might arise at this point, such as:

 

  • How do I create my own generic classes?

  • Can I constrain the types placed in a generic class or passed to a generic method? Suppose for instance, that you have a program with three classes in it called Employee, Person and Building. Can I create a method that will accept a list of type Employee, or type Person, but not a list of type Building?

  • We’ve seen generic classes and generic methods. What about interfaces or delegates, do they have a place in this world?

  • Can I use reflection to discover more about the generic types I’m passed at run time?

  • Etc

     

These questions, and many more, are all grist for the mill. I’ll take a look at least some of them in future blogs, and I will continue to explore the issues touched on in this introductory article. 

 

Read the second article in this series.