Welcome!

.NET Authors: Liz McMillan, Mark O'Neill, Peter Silva, Yakov Werde, Matthew Pollicove

Related Topics: .NET

.NET: Article

DNDJ Feature — Introducing C# Generics

Leverage code reuse without sacrificing type safety

You can apply constraints to each type parameter separately and, for a type parameter T, the constraint begins with the syntax, "where T :". The list continues as comma-separated items beginning with an optional base class followed by any interfaces. Following these, you can list a default constructor constraint using "new()" - which requires the type parameter to support the public default constructor. Finally, you may add a reference or value constraint with a class or struct keyword. The class Test<T> shows multiple constraints.

class Test<T> where T : MyBase, IComparable, IMyInterface, new()

Use constraints with caution since they reduce generality and can prevent code reusability. Operators, such as the == operator, can't be a constraint. In List3<T> we commented out the code:

    //if (obj == NO_ITEM)
    // throw (new Exception("Cannot hold the default() value."));

This code fragment isn't permitted because the type parameter T can't be guaranteed to have the == operator available. Our original design of the list collection is using default(T) as the value to return if the list is empty. For reference types, default(T) resolves to a null and produces zero-filled contents for value types. Since we can't constrain type T to require the == operator, the compiler won't allow its use and we remove it. However, this code disallowed entering the default(T) value into our List3<T> collection and we've now introduced ambiguity. When Remove() returns the default(T) value, how will the code using the collection distinguish whether the list is empty, or that the default value has actually been removed?

When storing only reference types such as String and Object, it was reasonable for our collection to reject storing null values. However, if the fully generic List3<T> is used as List3<int> it makes little sense to disallow adding the value '0' - which is default(int) - to the collection. Although we don't undertake it here, the right approach to this problem is to redesign the collection to be able to hold any value, including default(T), and to use another mechanism to indicate that the list is empty.

In the boxing process mentioned previously involves taking the primitive and creating a simple object for it from heap memory. When we use generics with primitives, no such process is necessary and we get a modest performance gain for doing things in the more elegant way. The performance gain is negligible in typical programs doing network and file operations, but for programs that spend the bulk of their time looping through collections of primitive types the gain is dramatic. The type information about generics, the number of type parameters and their constraints, is fully supported by the C# 2.0 bytecode and runtime. This provides type safety for reflection even when using third-party assemblies containing generics.

Generic Collections
In our illustration we used only a minimal generic collection. Due to their utility, collections are often the first classes to become generic. C# 2.0 provides the following collection classes in the System.Collections.Generic namespace:

  • Dictionary<K,V> : A key and value collection
  • LinkedList<T> : A linked list
  • List<T> : A list (implemented on an array)
  • Queue<T> : A queue
  • SortedDictionary<K,V> : A key and value sorted collection
  • SortedList<T> : A sorted linked list
  • Stack<T> : A stack
There is also System.ComponentMode.BindingList<T>, which can fire state change events. C# defines several generic interfaces including IEnumerable<T>, which enables all collections to support enumeration and C#'s for each keyword. This is how it works on a LinkedList:

    LinkedList<int> a = new LinkedList<int>();
    a.AddLast(1);
    a.AddLast(2);
    foreach (int item in a)
    {
      // Do something with item
   }

Both System.Collections.Generic.List<T> and System.Array have many useful generic methods, many designed to use four generic delegates in the System namespace.

public delegate void Action<T>(T t);
public delegate int Comparison<T>(T x, T y);
public delegate U Converter<T, U>(T from);
public delegate bool Predicate<T>(T t);

Combining generic delegates with utility methods enables powerful processing of collections. Unfortunately, collections other than List<T> and Array lack equivalent generic utility methods but there are third-party generic libraries, such as Recursion Software's C# Toolkit, that support C# generics and provide extensive additional collections, algorithms, functions, and predicates.

Conclusion
We focused on the tension between writing reusable code and maintaining type safety, and then showed how C# 2.0 provides generics to address the problem elegantly. With generics, you can employ the type safety features of C# without preventing effective code reuse where the data type varies.

Resources

  • Juval Lowy. "An Introduction to C# Generics."
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/csharp_generics.asp
  • Generics. http://msdn.microsoft.com/vcsharp/2005/overview/language/generics/
  • Bill Venners with Bruce Eckel. "Generics in C#, Java, and C++ - A Conversation with Anders Hejlsberg, Part VII."
    www.artima.com/intv/generics.html
  • "An Extended Comparative Study of Language Support for Generic Programming."
    www.osl.iu.edu/publications/prints/2005/garcia05:_extended_comparing05.pdf
  • Andrew Kennedy and Don Syne. "Design and Implementation of Generics for the .NET Common Language Runtime."
    http://research.microsoft.com/projects/clrgen/generics.pdf
  • Standard ECMA-334. "C# Language Specification." 4th edition. June 2006.
    www.ecma-international.org/publications/standards/Ecma-334.htm
  • 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.