Welcome!

Related Topics: .NET

.NET: Article

DNDJ Feature — Introducing C# Generics

Leverage code reuse without sacrificing type safety

How often have you wanted to reuse some code you previously wrote but it didn't quite fit in your current project? Code reuse is an oft-touted benefit of modern object-oriented programming. With the advent of generic support in the C# language appearing in the .NET Framework 2.0 developers have new leverage for writing code that can be reused without compromising type safety.

About Type Safety
A key benefit to languages that support type checking is type safety. Type safety at build time and/or runtime prevents code from manipulating data of an incorrect type. As part of its type safe approach, C# detects type mismatch errors at build time in the compiler and at runtime in the Common Language Runtime (CLR). The following is a type mismatch error that C# will catch at build time:

    Object a = new Object();
    String b = a;

The second line will generate a compiler error because a plain Object can't be used as a String while maintaining type safety. Unfortunately, build-time type checking is ineffective if explicit casts are used. A cast lets the programmer circumvent the type checking at build time. For programming languages without runtime type checking this can result in invalid operations on data types having unpredictable consequences, such as manipulating a fragment of a String data type as if it were an int. C# provides type safety checks at runtime as well as build time. Here is a type mismatch error that won't be caught until the program is run:

    Object a = new Object();
String b = (String)a;

The program will build without errors. However, when running the program, it will generate an InvalidCastException on the second line because the Object referred to by 'a' can't be converted to the type String.

This type mismatch error is blatant but serves to remind us to avoid explicit casting since it negates the type safety provided at build time. Relegating these errors to program runtime incurs additional productivity cost (See "The Zen of Strong Typing" sidebar). In fact, now that we have the flavor of the type checking performed at build time, languages providing type safety features shouldn't lead us into situations that require us to turn off type safety by introducing such casts.

Reusing Code Without Generics
Evan, a programmer hired to write software for a local bakery, needs to manipulate strings. The implementation of a minimal collection is shown in Listing 1. The collection is a singly linked list called List1. Since Evan is only interested in storing String type data at this point, he builds the collection to fit the need to store String data. The bold text in Listing 1 shows the locations where the collection is specific to the type String.

Now Evan can use the collection as follows:

    List1 aList = new List1();
    aList.Add("a");
    aList.Add("b");
    for (String item = aList.Remove();
      item != List1.NO_ITEM;
      item = aList.Remove())
    {
      // Do something with item
    }

List1 is type safe, meaning it accepts and returns String types and this type safety is enforced at build time.

Eventually Evan has to track the products waiting for the oven, items in the oven, and goods under the display counter. This is an opportunity for reusing the previously programmed collection, List1. The problem is he now needs to collect BakeItems, not Strings. Evan could copy the source code to form a new collection by replacing String with BakeItem, but this approach has dire consequences. If an error is found later then both copies of the source code will have to be fixed. The baker has also indicated that Evan will also have to track waiting customers, inventory shipments, and other things. Writing separate collection code for each data type is clearly not desirable.

Evan decided to rewrite the collection with the most generally available type, Object. This results in a List2 class where the only difference is the replacement of all uses of the data type String with Object noted in bold text. Below is a fragment of List2.

    public class List2
    {
      public static Object NO_ITEM = default(Object);
      internal class ListNode
      {
public ListNode next = null;
public Object obj = NO_ITEM;
      }
      // ...

      public virtual void Add(Object obj)
      // ...

      public virtual Object Remove()
      // ...

Deprecating List1 in favor of List2 requires refactoring the earlier string collecting code.

    List2 aList = new List2();
    aList.Add("a");
    aList.Add("b");
    for (Object item = aList.Remove();
      item != List2.NO_ITEM;
      item = aList.Remove())
    {
      String theValue = (String)item;
      // Do something with theValue
    }

The advantage of this rewrite is that we can use List2 for any type of thing we want to collect. For primitive types, C# will perform a conversion to an appropriate object in a process called boxing. This lets us transparently handle primitive types as if they were objects. Therefore, we could use List2 with int as follows:

    aList.Add(1);
    aList.Add(2);
    for (Object item = aList.Remove();
      item != List2.NO_ITEM;
      item = aList.Remove())
    {
      int theValue = (int)item;
      // Do something with theValue
   }

For Evan, List2 seems well prepared for String, int, BakeItem, Customer, or anything else he might need. However, note that a cast is now necessary to convert from the most general type, Object, to the actual type, String or int in the code fragments. Recall that explicit casting is just the situation we want to avoid so we don't negate the type checking done at build time. For instance, during the development effort, Evan accidentally added an instance of a data type called BakeItem to a List2 collection meant only to contain Customer elements. The error will appear as an InvalidCastException at runtime, with no indication of problems at build time.

This weakens the original intent of having multiple different homogenous collections in a way that allows any individual collection to accept unintended data types. Even with the explicit casts that negate build time type checking, this approach to reusing code is superior to the alternative of writing code for a multitude of type-specific collections. This code reuse versus type safety tension was a problematic state of affairs with C# 1.1. With the addition of generics to C# 2.0 there is now a better solution to hand.


More Stories By Robert R. Hauser

Robert R. Hauser has a masters degree in computer science specializing in Artificial Intelligence with more than 15 years of development experience in C, C++, C#, and Java, building software for networking, filesystems, middleware, and natural language processing. He works at Recursion Software, which is focused on providing a next generation application platform for .NET and Java.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.