C# Generics in Rotor

If you want to try using generic classes and methods in C#, you don't have
to wait. Microsoft Research has released an experimental implementation, called
Gyro. It is available
as a patch to the beta version of
Rotor (the shared source edition of the Microsoft CLI).

Installing Gyro

To get a working installation, first download and install the Beta Refresh
of Rotor. You can find the link at the bottom of the current Rotor page. You
will need Perl and C++ to compile both Rotor
and Gyro.

Then download Gyro into a separate directory. This contains updated versions
of many files in the Rotor release. Next, run the install_gyro script with
these two directories as arguments. The script copies the Gyro files into the
Rotor tree, backing up the existing Rotor files, so if you have made your own
modifications to the Rotor code, you won't lose them by installing Gyro. If
you later want to uninstall Gyro, the uninstall script will undo the installation.

After installation, build Gyro in the same way as you would Rotor; i.e., cd
to your Rotor tree, run the env script, and then run buildall. If you have
previously compiled Rotor, you will need to run buildall -c, which does a
clean build. (The release notes tell you to use -c on FreeBSD; you probably
need it even on XP.)

Note that although Microsoft have released version 1.0 of Rotor, at the time
of this writing, Microsoft Research has not updated Gyro to match the new release.
The installation script should warn you if you try to install Gyro over the
wrong release of Rotor, but it's best to make sure yourself.

A First Program

Generic classes add a way to build a relationship between two classes. Inheritance
(and interface implementation) model the "is-a" relationship, when one class
is a subtype of another. Member variables model the "has-a" relationship, when
an object of one class includes an object of the other class as a sub-component.
A generic class typically models the "contains" or "of" relationship, where
an object is a collection of objects of the parameter type; e.g. an ArrayList
"of" window objects, or a set "of" query results.

The samples packaged with the Gyro release include some generic collection
classes. You can find them in the Samples/GCollections directory. (The build
process will have compiled these, and put the executables in the Build/v1.86fstchk.rotor/generics
directory.) Let's look at the ISet interface:

The identifier T here is a formal type parameter. This interface
says that an object that implements ISet will have three methods,
in addition to those in ICollection. Add takes an
argument of type T, whatever T is, and adds it to
the set. Remove and Contains do the obvious things,
given an argument of type T. ISet doesn't say what
the type T is; it just says that if we have a set of a given type,
then all operations on that set must use the same type.

Let's create an example. The HashSet class implements ISet.
We needn't worry about how it's implemented; we're just going to use it.

You create a new set by specifying actual types to replace the formal parameter:

using GCollections;
ISet<string> MyStringSet = new HashSet<string>();

This creates a set of strings, called MyStringSet. We can use
this to write a new variation on the classic first program. Note that we can
use the C# foreach construct even with a generic class defined
in user code.

To compile this, first copy GCollections.dll from Build/v1.86fstchk.rotor/generics
to the same directory as the program file. Then compile and run the program
using these commands:

csc /r:GCollections.dll MyStringSet.cs
clix MyStringSet.exe

You always have to use the clix command when running Rotor or Gyro executables.
This is one of the differences between Rotor and the production version of .NET.

Safety

For such a simple program, the use of generics hardly makes much difference,
beyond perhaps cluttering the code a little. But if we allowed MyStringSet
to be accessed from another class, generics would provide more type safety than
a non-generic set. If someone attempts to add a non-string to MyStringSet, he or she will see a type error. The equivalent non-generic code would compile
successfully, because non-generic sets can contain any objects. The error would
only be apparent when the code attempted to use the value in the set. Of course,
testing should catch this case, but we all know that testing doesn't always
work!

Here's a more illustrative example. Suppose we a have stack-based calculator,
where the stack always contains doubles. The SumTop method pops
the top two values from the stack and returns the sum of the result:

The generic version traps the mistake at the point where the code attempts
to push the string value. The GCollections library doesn't include a Stack
class per se, but its ArrayList class does act as a stack, so we
can use that. (The GCollections library does not mirror the standard C# collections.
Nor is it in any way endorsed by Microsoft as a replacement.)

Performance

This code is more efficient, as well as safer. The signature of the SumTop
method now specifies the type of objects stored in the Stack/ArrayList. Therefore,
the code doesn't need any downcasts, and doesn't need the dynamic type checks
of the original.

What's more, the code that creates the stack is more efficient, as well. When
you Add a double to a non-generic stack, the system has to box the value to
make it an object. It allocates space on the heap for the object, and copies
the value into the new object. This is expensive. In the generic case, the stack
can only hold doubles, and so there is no need to box the values.

You can see this if you disassemble the executables with ildasm. Here is the
relevant part of the IL for the non-generic Main method:

The box instruction is absent. You can also see how the type parameter is passed
through the C# compiler to the IL. If you have another compiler that uses generics
(such as the language
F#, also from Microsoft Research), you can use this code from that language
as well as C#. The !0 in the signature of Add references
the first type argument of the class (type arguments are indexed from 0, as
you would expect).

Conclusion

I've shown you a first step with using Gyro to explore the world of .NET generics.
Gyro's samples show many more examples, including generic methods. Explore further,
and you can see how to access these objects via reflection, for example. Try
mixing generics with the other features of .NET and C#, and see how powerful
they can be.