WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

New Language Features in C# 2.0, Part 2

by Matthew MacDonald, coauthor of ASP.NET in a Nutshell, 2nd Edition
04/12/2004

The first part of this series introduced three new C# language features: anonymous methods, iterators, and partial types. In this second part, we'll tackle the last and most exciting new feature, Generics.

Understanding Abstraction

In a strongly typed programming language like C#, you have two choices for manipulating objects:

  • Use the most specific type possible. For example, if you want to manipulate customer information, use references of type Customer.
  • Use a more generic type. This might be a base class or an interface that more than one class shares. For example, if you need to manipulate LocalCustomer and PremiumCustomer references, treat them both in the same way by using the ICustomer interface.

Usually, the first approach gives you the most control because you can access every aspect of the object. However, the second approach has a different advantage. It lets you write generic logic that can be used with different types of objects, reducing the total lines of code you need to write and the complexity of your solution. In programming speak, your code attains a higher level of abstraction, which makes it easier to reuse and maintain.

The problem with programming generically is that sometimes there isn't a useful base class or interface that's shared by all the types you want to support. Usually, the only solution is to build a weakly typed class that treats everything as the base type Object.

Related Reading

C# in a Nutshell
By Peter Drayton, Ben Albahari, Ted Neward

The perfect example of this problem is the System.Collections.ArrayList class. ArrayList is an all-purpose dynamically self-sizing collection. It can hold ordinary .NET objects or your own custom objects. In order to support this, ArrayList treats everything as the base Object type. The problem is there's no way to impose any restrictions on how ArrayList works. For example, if you want to use the ArrayList to store a collection of Customer objects, you have no way to be sure that a faulty piece of code won't accidentally insert strings, integers, or any other type of object. These mistakes won't raise an exception, although they might lead to more subtle and insidious errors later on. As a result, one of the most common ingredients in a .NET application is custom collection classes that derive from ArrayList but are strongly typed. The .NET class library is swamped with dozens upon dozens of strongly typed collections classes.

C# 2.0 finally offers a solution to this problem with a new language feature called Generics.

Generics 101

In C# 2.0, you avoid these headaches by creating classes that are parameterized by type. In other words, you create a class template that supports any type. When you instantiate that class, you specify the type you want to use, and from that point on, your object is "locked in" to the type you chose. (Behind the scenes, .NET actually creates a new strongly typed class dynamically at runtime.)

To see generics in action consider the following example, which shows the template for a typesafe ArrayList:

public class ObjectList<ItemType> : CollectionBase 
{
  private ArrayList list = new ArrayList(); 
  public int Add(ItemType value)
  {
    return list.Add(value);
  } 

  public void Remove(ItemType value)
  {
    list.Remove(value);
  } 

  public ItemType this[int index]
  {
    get
    {
      return (ItemType)list[index];
    }
    set
    {
      list[index] = value;
    }
  }
}

The ObjectList class wraps an ordinary ArrayList. However, it provides strongly typed Add() and Remove() methods, along with a strongly typed indexes. These members use the type ItemType, which is really just a placeholder for whatever type you choose when you create the class. This placeholder is defined in angled brackets after the name of the class.

Here's an example of how you could use the ObjectList class to create an ArrayList collection that only supports strings:

// Create the ObjectList instance, and 
choose a type (in this case, string).
ObjectList<string> list = new ObjectList<string>();

// Add two strings.
list.Add("blue");
list.Add("green");

// The next statement will fail because it has the wrong type.
// In fact, this line won't ever run, because the compiler
// notices the problem and refuses to build the application.
list.Add(4);

Generics is a feature that's supported across all first-class .NET languages, including VB .NET (unlike the other C# language features). Incidentally, the .NET Framework designers are well aware of the usefulness of generic collections, and they've already created several that you can use. You'll find them in the new Systems.Collections.Generic namespace. Almost every type in this namespace duplicates a type from the System.Collections namespace. The old collections remain for backward compatibility.

Advanced Generics

There's no limit to how many ways you parameterize a class. In the ObjectList example, there's only one type parameter. However, you could easily create a class that works with two or three types of objects, and allows you to make both of these types generic. To use this approach, just separate each type with a comma (in between the angle brackets at the beginning of a class).

For example, consider the following ObjectHashTable class. It allows you to define the type you want to use for the items you are storing (ItemType) and the keys you are using to index types (KeyType).

public class ObjectHashTable<ItemType, 
KeyType> : DictionaryBase
{ ... }

Another important feature in generics is the ability to use constraints, which can restrict what types are allowed. For example, you might want to create a class that supports any type that meets a certain interface. In this case, just use the new where keyword.

Here's an example that restricts the ObjectList class so that it can only use serializable items. (One reason you might use this approach is if you want to add another method in ObjectList that requires serialization, such as a method that writes all the items to a stream.)

public class ObjectList<ItemType> : CollectionBase 
  where ItemType : ISerializable
{ ... }

You can define as many constraints as you want, as long as you separate them with commas. Constraints are enforced by the compiler.

Incidentally, generics don't just work with classes. They can also be used in structures, interfaces, and delegates. In fact, you can even use generics to parameterize a method by type, as shown here:

public ItemType GenericFunction<ItemType>(ItemType 
item)
{
  // (Process item here.)
  return item;
}

The code that calls GenericFunction() defines the type, which is then used for a parameter to the function and a method.

string input = "...";
string output = Obj.GenericFunction<string>(input);

This technique isn't quite as useful as using generic types in a class, but it saves the overhead and potential problems of down-casting and up-casting your types.

Summary

For a more detailed look at the C# language and the advanced wrinkles in generics, surf to the C# Language Page on MSDN at http://msdn.microsoft.com/vcsharp/team/language. You'll find a link that allows you to download a detailed white paper on the new C# language specifications, along with useful insights from the C# language designers in the "Ask a Language Designer" section of the site.

Matthew MacDonald is a developer, author, and educator in all things Visual Basic and .NET. He's worked with Visual Basic and ASP since their initial versions, and written over a dozen books on the subject, including The Book of VB .NET (No Starch Press) and Visual Basic 2005: A Developer's Notebook (O'Reilly). His web site is http://www.prosetech.com/.


Return to ONDotnet.com