AddThis Social Bookmark Button

Print

C# Generics in Rotor

by David Berry
12/02/2002

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.)

Related Reading

Programming C#
By Jesse Liberty

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:


public interface ISet<T> : ICollection<T> {
    bool Add(T item);      // return true if item was added
    T Remove(T item);      // return removed item
    bool Contains(T item);
}

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.


using GCollections;

class M {
    public static void Main() {
        ISet<string> MyStringSet = new HashSet<string>();
        MyStringSet.Add("hello");
        MyStringSet.Add("world");
  	foreach (string i in MyStringSet)
            System.Console.WriteLine(i);
    }
} 

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:


    public static double SumTop(Stack s) {
        double d1 = (double)s.Pop();
        double d2 = (double)s.Pop();
        return d1+d2;
    }

It's clear from the code that this method is downcasting the values that it pops from the stack. If the stack should contain a value that isn't a double, we get a runtime error.

Here's a simple test program. Try it.


using System.Collections;

class M {
    public static void Main() {
        Stack MyStack = new Stack();
        MyStack.Push(2.0);
        MyStack.Push("2.0");	// Runtime Error
        System.Console.WriteLine(SumTop(MyStack));
    }

    public static double SumTop(Stack s) ...

}   

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.)


using GCollections;

class M {
    public static void Main() {
	ArrayList<double> MyStack = new ArrayList<double>();
	MyStack.Add(2.0);
	MyStack.Add("2.0");	// Compile Error
	System.Console.WriteLine(SumTop(MyStack));
    }
 
    public static double SumTop(ArrayList<double> s) {
        double d1 = s.Remove();
        double d2 = s.Remove();
        return d1+d2;
    }
}   

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:


    IL_0006:  ldloc.0
    IL_0007:  ldc.r8     2.
    IL_0010:  box        [mscorlib]System.Double
    IL_0015:  callvirt   instance void
      [mscorlib]System.Collections.Stack::Push(object)

Compare this with the corresponding code for the generic version (you'll have to correct the compile error first, of course):


    IL_0016:  ldloc.0
    IL_0017:  ldc.r8     2.
    IL_0020:  callvirt   instance bool class
      [GCollections]GCollections.ArrayList<float64>::Add(!0)

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.

David Berry


Return to ONDotnet.com