Tuesday, February 3, 2009

C# 4 is NOW!

Generic type variance support just landed in mcs. This is a C# 4 language feature.

You can give it a go by checking out SVN trunk and compiling your variant code with gmcs -langversion:future.

Well, this adds compiler support for variance but the Mono VM isn't up to speed on its variance handling. This means that you can compile the code but it won't actually run on Mono (until we fix that, which I am also doing). You can run it on the .NET 2.0 VM.

Generic type variance is like this:

Let's say I have some IEnumerable<string>, like so:

IEnumerable<string> myStrings = GetSomeStrings ();

Now let's say I have some other method which takes an IEnumerable<object>, like so:

void DoStuff (IEnumerable<object> someObjects)
    foreach (object o in someObjects) {
        // do some stuff with each object

POP QUIZ: Can I pass myStrings to DoStuff in C# 3? Strings are objects, right? And IEnumerable<T> is just a way of getting Ts. So if strings are objects, and IEnumerable<string> just returns strings, then we can also say that it returns objects. Just like IEnumerable<object>. So it should work, right?

ANSWER: Negatorz!

This is a problem of generic type variance. There are two kinds of variance: covariance and contravariance. The above example is covarant, meaning that you want to broaden the type of an output. Contravariance is the opposite: narrowing the type of an input. Let's consider a delegate:

delegate void Handler<T> (T input);

And let's say that we have some Handler<object>:

Handler<object> myHandler = delegate (object o) {
    // do something with the object

Now let's say that we have a method with takes a Handler<string>

void HandleStrings (Handler<string> handler, IEnumerable<string> strings)
    foreach (string s in strings) {
        handler (s);

We want to pass myHandler to HandleStrings. A Handler<object> takes objects, and strings are objects, so anything which is a Handler<object> should also be a legal Handler<string>. This is an example of contravariance.

It may surprise you to know, but the CLI has supported generic type variance since version 2. The rules are:
  • Variant type parameters are only allowed in interfaces and delegate types.

  • Contravariant type parameters can only be used as by-value method parameter types.

  • Covariant type parameters can only be used as method return types and generic arguments to inherited interfaces.

  • Only reference types are variant (this isn't explicitly stated in the spec, but it is the case).

  • Languages may choose to ignore variance and treat all generic parameters as invariant.

For whatever reason, the C# language team has so far chosen not to support generic type variance. Well, that will be changing in 2010. The preview given by Anders Hejlsberg at PDC '08 revealed that C# 4 will finally support variance. But who wants to wait? Especially considering that this has been a .NET VM feature since 2006. So you can now use this in gmcs if you pass -langversion:future.

Covariance (which, as you will remember, can only be used as a method return type or as a generic argument to an inherited interface) is denoted with the "out" keyword before the type parameter identifier:

interface IFoo<out T> : IBar<T>
    T Bat { get; }

Contravariance (which is legal only as the type of a by-value method parameter) is denoted with the "in" keyword:

interface IFoo<in T>
    void Bar (T bat);

So there you go! Now we just need to get Mono's VM variance support polished off. I'm sure I'll have good news for you about that shortly. Thanks goes to Marek Safar for reviewing patches. If you have questions about how or why variance works (it's kind of tricky to get your head around), leave a comment. I might do a post delving into all of the little rules behind variance.