C# Corner

Generics: Move Beyond Collections

Generics can solve many more problems than collections. Use generics to write code once and reuse it more easily.

Technology Toolbox: C#

Almost every article or lecture about generics touches on collections and shows how to build a Stack<T>. Collections and stack building are an obvious choice, but generics have many other uses.

Generics and the C# language give you many ways to make working with generics easier. I'll show you a few of the techniques that generics make possible, but there are many more. Applying these techniques and extending them with your own ideas will enable you to write code that you can reuse in many different ways. The end result: You won't write less code, but write your code only once, and write it in such a way that you can reuse it in as many different situations as possible.

The easiest way to learn the different things you can do with generics is to look at some of the uses of generics in the .NET Framework outside of the collections classes. One of the simplest timesavers in the 2.0 Framework is EventHandler<TEventArgs>. In the 1.1 Framework, it requires three lines of code to declare a new event type:

// declare the delegate type
public delegate void MyEventHandler
	(object sender, MyEventArgs args);
// declare the event:
public event MyEventHandler OnRaiseMyEvent;

Framework 2.0 cuts the effort almost in half:

public event EventHandler<
	MyEventArgs> 
	OnRaiseMyEvent;

Yes, it's a small savings, but you get to reuse those savings every time you create a new event type, and those savings add up over the course of a large application. The EventHandler<TEventArgs> definition also provides some additional type safety. For example, consider how you create the definition:

[SerializableAttribute] 
public delegate void EventHandler<TEventArgs> (
	object sender,
	TEventArgs e)
 where TEventArgs : EventArgs

The 2.0 Framework designers realized that you might want to serialize an event definition, so they added the Serializable attribute declaration to the EventHandler<TEventArgs>. Better still, they added a constraint to TEventArgs that ensures TEventArgs derives from System.EventArgs. Using this delegate definition means that you cannot define an event handler that doesn't follow the .NET Framework event handler signature convention. You do less work, get better code, and enforce the proper event signature.

The 2.0 .NET Framework has many other additions that rely on generics. For example, the IComparable interface received a type safe update for 2.0. In Framework 1.1, you implement IComparable like this:

int IComparable.CompareTo(object obj)
{
	if (false == (obj is MyType))
		throw new ArgumentException(
		"compared object is not the 
		proper type", "obj");
	MyType other = (MyType) obj;    
	if (other == null)
		return 1; // this object is
			//greater than null.
	else
		return CompareTo(other);
}

public int CompareTo(MyType other)
{
	// comparisons elided
}

The 1.1 IComparable code contains quite a bit of redundancy, and you need to code somewhat defensively for it to work properly with the 1.x signatures. First, you should implement the IComparable.CompareTo() method explicitly because it's not type-safe. The parameter could be anything: It might not be MyType. You need to trap that error condition correctly. If MyType is a value type, you get to pay the performance penalty for boxing and unboxing the parameter as part of the method call.

All the extra code means you should create a type-specific version of CompareTo(). The compiler ensures that the proper comparison is called whenever possible.

The 2.0 Framework version is much simpler:

int IComparable<MyType>.CompareTo(MyType other)
{
	// comparison elided
} 

This example elides the code that is specific to your type, so you won't see the same percentage savings. But look at the 1.1 versions of IComparable.CompareTo(). Everything in that method concerns itself with implementing the interface signature correctly. Nothing is about handling the specific implementation of CompareTo(). The 2.0 version that relies on generics lets you concentrate on the specific logic you need for your types.

It's easy to find other examples like this in the 2.0 Framework. For example, IEquatable<T> provides a better signature than Equals():

public bool IEquatable.Equals(T other);
	// is better than
public virtual bool Equals(obj other);

That said, it will be more fun to spend the rest of this article showing you other kinds of ways to leverage generics in the Framework.

Generic Types Often Simpler Types
The rules of C# say that you can only have one return value. And yet, you'll often write routines that return two or more items, such as first and last name, name and ID, or city and temperature. It's a common pattern, yet the language doesn't have a clear solution.

Before generics, you had two possible paths you could take. The quickest path was to resort to out parameters:

public void GetCustomerById(int id, 
	out string firstName, out string lastName,
		out string phoneNumber)
{
	// elided.
}

The three output parameters give you a genuinely ugly method signature, and it gets worse when you call the code: If the method fails, did any of the out parameters get set? If so, which ones?

There is a similar pattern at work in the .NET Framework family of TryParse methods. For example, double.TryParse has this signature:

public static bool TryParse (
	string s,
	out double result
)

The value of result gets set if TryParse returns true. If the input string isn't a number, then TryParse returns false, and the value of result is meaningless.

In C# 2.0, you can create a Tuple struct that contains two fields of any type (Listing 1). Note that the Tuple struct is constructed on top of the recommendations I made earlier: It implements IEquatable<Tuple<T1, T2>>. Yes, you can build generic classes out of other generics, but there's nothing special about them that precludes using generics as you use anything else.

You can use the Tuple class to wrap the TryParse method into a more efficient signature:

public static Tuple<bool, double> MyTryParse(
	string input)
{
	double val;
	bool b = double.TryParse(input, out val);
	return new Tuple<bool, double>(b, val);
}

Again, it's not the most earth-shattering change to your coding style that you'll ever see. But you might find yourself using this class so often that it's worth the shift:

Tuple<string,DateTime> GetCustomerBirthday(int id);

The more you see this pattern, the easier it is to recognize it and apply it. One day, you might find you need more than two objects in your Tuple. It's a fairly easy addition to create a new type, Tuple<T1, T2, T3>. Or any number that you need.

The one downside to the Tuple<T1, T2> type is that it doesn't convey any semantic information. Tuple<int, string> could be anything. You can use the using statement to convey that semantic information:

using ParseDoubleResult = MyGenerics.Tuple<bool, 
	double>;

You can take advantage of this type of using statement only with a fully closed generic type. You can't use this declaration with an open generic type. For example, this code won't compile.

// Won't compile
using MyList = System.Collections.Generic.List<T>;

You can create a using alias for any of the closed generic types if you dislike the extra angle brackets in your code. You can also define an alias for any of the closed generic types you use. Defining an alias can increase the readability of your code tremendously.

Create Powerful Classes Quickly
One of the most common misconceptions about generics is that you must create a generic type only when you want to do something generic. That's not true. You can create generic methods in any class. For example, assume you want to create a simple static class that contains some common methods you might want to use on any type (Listing 2). The core framework already contains methods to find the max and min of numbers, so now you can add Swap:

double d1, d2;
Utility.Swap(ref d1, ref d2);

You can also find the max of two strings:

String m = Utility.Max("Bill Wagner", 
	"Patrick Meader"); 

You don't need to specify the type parameters on the code that calls Min, Swap, or Max. That's because the compiler can determine the particular instantiation of the method by the parameters passed to the method. In fact, the compiler also catches mistakes if you specify mismatched types:

// won't compile. Type checking is good.
int f = Utility.Max(5, "a string");

You probably wouldn't make that mistake, but such mistakes can creep into a large application with many different types.

The overall effect of the Utility class is a type that continues to grow more useful without adding code. Whenever you use one of these methods with any new class or struct, it just works. The compiler knows to create a new version of the particular method whenever you try to call it with different parameter types.

Min, Max, and Swap are useful, but let's look at a more substantial example. This final sample shows one class that reads and writes any type using the XML Serializer (Listing 3). You've probably read and written different types using the XMLSerializer dozens of times. Each time find a similar set of read and write routines, change the types involved, and use them again. But that's so inefficient. Suppose you find a bug later; you'll need to fix every possible location where this code was copied. By creating a generic version, any bugs that might appear can be fixed in one location, and you're done.

There's one slightly more difficult item with using this ReaderWriter class: You must specify the type parameter on the call to CreateFromFile:

MyType value = 
ReaderWriter.CreateFromFile<MyType>("values.xml");

You must specify the type parameter because the compiler cannot infer which instantiation to call from the return value (just as it cannot infer different overloaded methods from the return value). But the effect is almost like magic. Each new instantiation of the methods adds new functionality to the existing class. And it's reusing the same source code, so you don't need to add code that you must maintain. You get to add functionality without increasing the size of the code base.

Choose the Best Method
If there is a downside to generics, it's that generic methods are often designed to work with the widest possible set of specific types. That's how you achieve the greatest reuse. Sometimes knowing more about the specific type means you can create a more specific implementation by taking advantage of a given type's features. The C# method resolution rules were designed to make it easier for you to ensure that the best method is chosen all the time.

A full discussion of resolution rules would be both lengthy and a little pedantic, so let's limit ourselves to the problem you face if you want to create a specific version of a method to override a generic version. Look back at the definition of Max<T> in the Utility class (Listing 2). You could call those methods using any of the numeric types. But chances are the versions of Min and Max defined in the Math class would be preferred. That's easy to fix: Add a non-generic version of Min and Max to the utility class that calls the more efficient method:

public static double Max(double first, double second)
{
	return Math.Max(first, second);
}

The compiler uses the generic methods only when a suitable non-generic method can't be found. This means you can take advantage of a particular type to create a better method, and this method can coexist with its generic counterpart. The developers that use your class need to remember only the generic signature, and the compiler finds the best match. This is a great deal for everyone.

Opening up your mind to this technique and the others presented on generics can help you create more powerful applications in less time. It will also enable you to write generic methods and classes much more frequently and achieve better code reuse. Other topics you might want to look at in this vein include defining delegates with generic parameters, defining your own generic interfaces, and writing your own generic factory classes.

About the Author

Bill Wagner, author of Effective C#, has been a commercial software developer for the past 20 years. He is a Microsoft Regional Director and a Visual C# MVP. His interests include the C# language, the .NET Framework and software design. Reach Bill at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube