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.