Wednesday, October 29, 2014

Covariance and Contravariance in C# - Part 2

In the previous post(http://softtechhelp.blogspot.in/2014/10/covariance-and-contravariance-in-c.html) we talked about the fundamentals of covariance and contravariance. In this post we will focus on more advance understanding of the concept.
Once we talk about covariance/contravariance, we should keep in mind that
1) Classes can not be covariant or contravariant.
2) Value type can not played around in covariant/contravariant manner.i.e. we can not talk about covariance or contravariance for IFoo<int>

In the previous post we talked about how Generic types can be created in covariant/contravariant manner. Today we will talk about how function/delegate can be used/created covariantly/contravariantly.

Delegates -
A delegate can be create in covariant or contravariant manner, or both together.
class Program
    {
        static void Main(string[] args)
        {          
            CovariantDel<Derived> d1 = GetDerived;                                 //1
            CovariantDel<Base> b1 = d1;                                                     //2
            b1();                                                                                             //3
            ContravariantDel<Base> b2 = PutBase;                                     //4
            ContravariantDel<Derived> d2 = b2;                                         //5
            d2(new Derived());                                                                      //6

            //Input Derived, Output Base
            CoVariantAndContravariantDel<Derived, Base> db1 = PutBaseGetDerive;  //7
            //Input Base, Output Base
            CoVariantAndContravariantDel<Base, Base> bb = PutBaseGetDerive;     //8
            //Input Base, Output Derived
            CoVariantAndContravariantDel<Base, Derived> bd1 = PutBaseGetDerive; //9
            //Input Derived, Output Derived
            CoVariantAndContravariantDel<Derived, Derived> dd = PutBaseGetDerive;//10
        }

        //Input Base and returns Derived
        public static Derived PutBaseGetDerive(Base b)
        {
            return b as Derived ?? new Derived();
        }
        public static void PutBase(Base b)
        {
            Console.WriteLine(b.ToString());
        }
        public static Derived GetDerived()
        {
            return new Derived();
        }
    }

    public delegate Tout CovariantDel<out Tout>();
    public delegate void ContravariantDel<in Tin>(Tin t);

    //Covariant return type and contravariant input
    public delegate Tout CoVariantAndContravariantDel<in Tin,out Tout>(Tin t);  
    public class Base { }
    public class Derived : Base { }  

Lets analyze the above code.
Covariance - CovariantDel is a delegate which is covariant, It is used to point to a method which will return type Tout, there wont be any input to the method, which ensures covariance.
If you see closely, this delegate type has same signature as Action delegate(details below).
Contravariance - ContravariantDel is contravariant delegate. If you see it has Tin as input parameter
and there is no value returned from it, hence clearly contravariant.
If you see closely, it is similar to Func<Tout>.
Both - CoVariantAndContravariantDel is a delegate is covariant for Tout and contravariant for Tin. If you see statement 7,8,9 and 10, all are valid, because of its dual nature. lets analyze them more closely -
PutBaseGetDerive - Expects a base and returns derived.
Statement 7 -
CoVariantAndContravariantDel<Derived, Base> db1 = PutBaseGetDerive;
db1 returns Base and take input as Derived. whereas PutBaseGetDerive expects a base and returns derived. How it is possible to assign PutBaseGetDerive to db1?
As mentioned in the delegate signature, Tin is contrvariant, which means, this type will be used ONLY as input. And Tout is covariant, means it will be used ONLY as return type.
Tin -> contravariant -> Derived , expectation -> Base, Always possible to get base object from derive, hence no issue.
Tout -> Covariant -> Base, actual return type is - Derived, Again simple to get base from Derive type.

Hence with the help of "in" and "out", compiler always make sure, that a correct type or its derived type is passed/returned.

Action delegate-
If you see the definition of Action, It takes input and returns void. Which clearly says the scenario of "in" Or Contravariance.
If we create an Action which can work on Basetype, We can always assign its instance to a reference of Action of Derived type.
class Program
    {
        static void Main(string[] args)
        {
            Action<Base> b = PrintBase;
            Action<Derived> d = b;
            d(new Derived());
        }
        public static void PrintBase(Base b)
        {
            Console.WriteLine(b.ToString());
        }
    }
    public class Base { }
    public class Derived : Base { }
If you recall from previous post we mentioned the similar behavior with IComparer.
Action is another

Func -
If you see the signature of Func it is has nature of being covariant and contravariant both.
   public delegate TResult Func<out TResult>(); // Covariant
   public delegate TResult Func<in T, out TResult>(T arg); // Both

In the above mentioned example, CoVariantAndContravariantDel is nothing but the Func. 

No comments:

Post a Comment