Feb 16, 2010

Covariant/Contravariant Support for generic interface .net 4.0

The introduction of generic in .net makes covariant/contravariant support a very interesting topic. Now .net 4, there are some support for generic interface. But covariant and contravariant for generic class is still not supported with good reason, so you can not write the following code in .net 2 and .net 4

[TestMethod]
public void no_covariant_support_for_generic_class()
{
    //can not be compiled
    List<object> list = GetListOfString();
    //if support, what if use write
    list[0] = 1; //this is not secure

}

public List<string> GetListOfString()
{
    return new List<string>();
}

[TestMethod]
public void no_contravariant_support_generic_class()
{
    //can not be compiled
    Process(new List<string>());
}

public void Process(List<object> input)
{
    //if this is support, what if user write
    input[0] = 1; //this is not secure
    
}

There reason of not supporting covariant or contravariant is that .net need to support type safety. But as long as, we can restrict user to use object in type safe fashion, we should allow it. .NET 4 provide covariant and contravariant support for generic interface and delegate by using "in" and "out" to decorate type parameter so that the interface can be only use the type parameter in "in" or "out" fashion. Here is an example

interface IHouse<out T>
{
   T Checkout();
}

In the above example, T can be only appear in the return type position. So you can compile the following code.

If you want write the following code, then will be a compiler error like "Invalid variance: The type parameter'T' must be contravariantly valid on 'Checkin(T)'. 'T' is covariant

interface IHouse<out T>
{
   T Checkout();
   void CheckIn(T input);
}

To fix this error, we can add new contravariant type parameter using "in", like the following, and change the client code as well. The K type parameter will only appear at the input parameter position.

interface IHouse<out T, in K>
{
   T Checkout();
   void CheckIn(K input);
}


class House<T, K> : IHouse<T, K>
{
   public T Checkout()
   {
     return default(T);
   }

   public void Checkin(K input)
   {
       Console.WriteLine(input.GetType());
   }
}

IHouse<string, object> x = new House<string, object>();
IHouse<object, string> y = x;
object rtn = y.Checkout();
y.Checkin("hello"); //it actually call x.Checkout(object input), which is fine

Ok, so now what is the big deal of the covariant and contravariant. The short answer is that, it make it possible for you to write code you think it make sense but you couldn't before .net 4. So now you can write the code below which is not possible in .net 2. Here is covariant example in .net 4


//in .net 2, you have to write
IEnumerable<string> list = new List<string> { "1", "2", "3" };
//in .net 4, you can write the following because of the "out" keyword, it support covariant
//public interface IEnumerable<out T> : IEnumerable
IEnumerable<object> list = new List<string> { "1", "2", "3" };

//but you still can not write this in .net 4,  
//Covariance and contravariance in generic type parameters are supported for reference types, //but they are not supported for value types.
IEnumerable<object> list = new List<string> { 1, 2, 3 };