Showing posts with label Covariant and Contravariant. Show all posts
Showing posts with label Covariant and Contravariant. Show all posts

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 };

compiled time support for Covariant and Contravariant in .net 2.0


In .net 2.0, A delegate can be associated with a named method, this facility provide support for covariant and contravariant as the following sample code shows.

        delegate void ProcessString(string item);
        delegate object GetObject();

        void RunObject(object item)
        {
            Console.WriteLine(item);
        }

        string RetrieveString()
        {
            return string.Empty;
        }

        [TestMethod]
        public void delegate_shortcut_suport_contraVariant()
        {
            //delegate with derived type parameter input <--(accept) method with base type parameter input
            ProcessString processString = this.RunObject;
            processString("string");
            //-->
            RunObject("string");
        }

        [TestMethod]
        public void delegate_shortcut_support_coVariant()
        {
            //delegate with base type output <--(accept) method with derived type output 
            GetObject getObject = this.RetrieveString;
            object returnValue = getObject();
            //-->
            object returnValue2 = this.RetrieveString();
        }