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. 

Covariance and Contravariance in C#

Co-variance and contravariance mainly talks about the reference conversion. As you know, its always possible to assign the instance of derived type object to a reference of base type, Or in other words we can pass a derived type where ever a base type is expected, but the other way round is not possible.
Covariance and contravariance talks about this condition which was not possible in the old c#.
Important points before we start -

1) Classes can not be covariant or contravariant.
2) Value type can not played around in covariant/contravariant manner.

Covariance -
In mathematical terms - if A -> B then Fn(A) -> Fn(B)
In c# generic terms -
Base > Derived, (i .e we can pass derived, where Base required)
then Generic<Base> > Generic<Derived>
(i.e. we can pass Generic<derived> where Generic<Base> is required)

Examples  -
class Program
    {
        static void Main(string[] args)
        {
           IFoo<Derived> foos = null;     //1
            IFoo<Base> general = foos;    //2
        }
    }
    public interface IFoo<out T>
    {
        T GiveMeFoo();
    }
    public class Base { }
    public class Derived : Base { }
In the above example, as we have defined IFoo with T as "out", statement 2 will not give any error, if we remove out keyword from IFoo, it is a compile time error.
That is because, mentioning "out" means, we are explicitly telling compiler that we are not going to pass T as parameter for IFoo, we will only take it out.
Now in the above example, if we try to get object of Base out from general, it will give us object of derived which in tern can be translated into Base, hence it works.

Moreover, if you try to write AddT(T t) in the IFoo, then it will be an error, because T is covariant, and you can not have it as a input parameter.

IEnumerable -
IEnumerable is also covariant by nature, because we always take elements out from the collection, we do not process or modify them within. It is defined like IEnumerable<out T>
Its always possible to write -
IEnumerable<Derived> d1 = new List<Derived>();
IEnumerable<Base> b = d;
It is very much similar to writing
Derived d = new Derived();
Base b = d;
Which is not very new from C# perspective.

Contravariance -
In mathematical terms - if A -> B then Fn(B) -> Fn(A)
In c# generic terms -
Base > Derived, (i .e we can pass derived, where Base required)
then Generic<Base> < Generic<Derived>
(i.e. we can pass Generic<Base> where Generic<Derived> is required) MAGIC...!!!

Example -
class Program
    {
        static void Main(string[] args)
        {
            IFoo<Base> foos = new Foo(); //1
            IFoo<Derived> general = foos; //2
            general.ModifyT(new Derived()); //3
        }
    }
    public interface IFoo<in T>
    {      
        void ModifyT(T t);  
    }
    public class Foo : IFoo<Base>
    {
        public void ModifyT(Base t)
        {
            throw new NotImplementedException();
        }
    }

    public class Base { }
    public class Derived : Base { }

The above code clears says, that the statement 2 is a valid statement, which is something new to us, that is where we see the beauty of Contravariance.
This is because we explicitly mentioned T as a "in" parameter, which means the T will be an input to the method in the interface, there wont be any method in the given interface which would return the T, i.e. T will not be take out in any circumstances.
If you see statement 2, we are using general object, which is pointing to foos(of type Ifoo<Base>) and we call method ModifyT passing Derived object in it(statement 3), It can easily execute ModifyT(base), because derive can always replace Base.

This facility is only because we know the T will always be "in" (input parameter).

ICompare -
ICompare is contravariant by nature - public interface IComparer<in T>
Which means we do not take anything out with this interface, we always pass values within and compare them.
class Program
    {
        static void Main(string[] args)
        {          
            IComparer<Base> compareBase = new BaseCompare(); //1
            IComparer<Derived> compareDerived = compareBase;//2
            compareDerived.Compare(new Derived(), new Derived());//3
        }
    }  
    public class Base { }
    public class Derived : Base { }
    public class BaseCompare : IComparer<Base>
    {
        public int Compare(Base x, Base y)
        {
            throw new NotImplementedException();
        }
    }
Analyze the above code, Where we can assign a Comparer of type Base to a Derived one.
That is because in statement 3, we can always pass object of derived type, where we expect base type, and hence it works.

Tricks -
As we know mathematical formula for both covariance and contra variance -

covariance  - if A -> B then Fn(A) -> Fn(B)
contravariance -  if A -> B then Fn(B) -> Fn(A)

We can use this to identify if the given condition is allowed or not.
Say IFoo is  covariant -
Covariant has a simple relation so -
Object of derive can be assign to reference of type Base
Object of  IFoo<Derived> can be assign to reference of type IFoo<Base>
Object of  IFoo<IFoo<Derived>> can be assign to reference of type IFoo<IFoo<Base>>
and so on.....

Say IFoo is contravariant -
contravariant has a opposite relation relation so -
Object of derive can be assign to reference of type Base
Object of  IFoo<Base> can be assign to reference of type IFoo<Derived>
Object of  IFoo<IFoo<Derived>> can be assign to reference of type IFoo<IFoo<Base>>
and so on.....

If you see closely, 2nd statement got reversed but 3rd one is straight again, it is because in case of contravariance we change the sign, 2nd statement is reverse one, 3rd will be straight, 4th will be again reversed and so on....
if A -> B
then Fn(B) -> Fn(A)
then Fn(Fn(A)) -> Fn(Fn(B))
then Fn(Fn(Fn(B))) -> Fn(Fn(Fn(A))) .....

Code -
  class Program
    {
        static void Main(string[] args)
        {
            IFoo<IFoo<Derived>> fooD = new FooD();
            IFoo<IFoo<Base>> fooB = fooD;      
        }
    }

    public interface IFoo<in T>
    {      
        void ModifyT(T t);  
    }

    public class FooD : IFoo<IFoo<Derived>>
    {
        public void ModifyT(IFoo<Derived> t)
        {
            throw new NotImplementedException();
        }
    }
    public class Base { }
    public class Derived : Base { }