Wednesday, October 29, 2014

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

No comments:

Post a Comment