# Tuesday, 10 August 2004
« Mono 1.0 | Main | JavaScript on IKVM »
Whidbey, Generics, Co- and contravariance

I came across an interesting Whidbey beta 1 CLR feature today. As far as I can tell this hasn't been documented anywhere yet, but the Whidbey beta 1 CLR supports covariant and contravariant generic type parameters.

This is a really interesting feature. If C# would support it (at the moment it doesn't), you'd be able to do something like this:

interface IReader<T>
{
  T Get(); 
}
interface IWriter<T>
{
  void Put(T t);
}
class Test<T> : IReader<T>, IWriter<T>
{
  T t;
  public T Get() { return t; }
  public void Put(T t) { this.t = t; }
}
class Driver
{
  static void Main()
  {
    Test<string> t1 = new Test<string>();
    t1.Put("covariance");
    IReader<object> rdr = t1;
    Console.WriteLine(rdr.Get());
    Test<object> t2 = new Test<object>();
    IWriter<string> wrtr = t1;
    wrtr.Put("contravariance");
    Console.WriteLine(t2.Get());
  }
}

Notice that in the first highlighted line we're assigning an object reference that implements IReader<string> to a local variable of type IReader<object>. This is covariance and it is type safe because the IReader<T> interface only returns T. In the second highlighted line we assign a reference that implements IWriter<object> to a local of type IWriter<string>, this is contravariance and this is also type safe, because IWriter<T> only accept arguments of type T.

Unfortunately, C# doesn't yet seem to support this feature, but if we manually write the IL, it'll run on the Whidbey beta 1 CLR (and it is fully verifiable):

.assembly extern mscorlib {}
.assembly test {}

.class interface public abstract
      'IReader`1'<+([mscorlib]System.Object) T>
{
  .method public abstract instance
          !T Get() cil managed {}
}

.class interface public abstract
      'IWriter`1'<-([mscorlib]System.Object) T>
{
  .method public abstract instance
          void Put(!T t) cil managed {}
}

.class public 'Test`1'<([mscorlib]System.Object) T>
      extends [mscorlib]System.Object
      implements class 'IReader`1'<!T>,
                 class 'IWriter`1'<!T>
{
  .field private !T v

  .method public virtual instance
          !T Get() cil managed
  {
    ldarg.0
    ldfld !0 class 'Test`1'<!T>::v
    ret
  }

  .method public virtual instance
          void Put(!T t) cil managed
  {
    ldarg.0
    ldarg.1
    stfld !0 class 'Test`1'<!T>::v
    ret
  }

  .method public specialname rtspecialname
          instance void .ctor() cil managed
  {
    ldarg.0
    call instance void [mscorlib]System.Object::.ctor()
    ret
  }
}

.method static void Main(string[] args) cil managed
{
  .entrypoint
  .locals init ([0] class 'Test`1'<string> t1,
                [1] class 'IReader`1'<object> rdr,
                [2] class 'Test`1'<object> t2,
                [3] class 'IWriter`1'<string> wrtr)

  newobj instance void class 'Test`1'<string>::.ctor()
  stloc.0
  ldloc.0
  ldstr "covariance"
  callvirt instance void class 'Test`1'<string>::Put(!0)
  ldloc.0
  stloc.1
  ldloc.1
  callvirt instance !0 class 'IReader`1'<object>::Get()
  call void [mscorlib]System.Console::WriteLine(object)

  newobj instance void class 'Test`1'<object>::.ctor()
  stloc.2
  ldloc.2
  stloc.3
  ldloc.3
  ldstr "contravariance"
  callvirt instance void class 'IWriter`1'<string>::Put(!0)
  ldloc.2
  callvirt instance !0 class 'IReader`1'<object>::Get()
  call void [mscorlib]System.Console::WriteLine(object)
  ret
}
 

Notice the plus and minus signs in the interface declarations to signal that a generic type parameter is covariant or contravariant.

Tuesday, 10 August 2004 16:03:46 (W. Europe Daylight Time, UTC+02:00)  #    Comments [6]