Monday, April 11, 2011

Covariance vs Contravariance


Variance is actually nothing new in .NET but .NET 4 has extensively used them in its framework and that makes it essential to understand. Variance actually provides you the implicit conversion (instead of explicit typecasting everywhere). 

Covariance happens when you assign something more derived in the hierarchy to less derived so that assignment compatibility is preserved for e.g. 

//Example of Covariance
//String (more derived type) is direcly assignable to Object(less derived type>
IEnumerable<string> stringCollection = new List<string>();

IEnumerable<object> objectCollection = stringCollection;
Or

IEnumerable<object> obj = new List<string>();


Contravariance is just opposite to Covariance and happens when you assign something less derived in the hierarchy to more derived type such that assignment compatibility is reversed for e.g.

//Example of Contravariance
//More derived type delegate is assignable to delegate containing less derived type
Action<object> o = WriteToConsole;
Action<string> o1 = o;

public static void WriteToConsole(object o){} 

So the question comes where they are actually supported in framework
1.    Covariance in arrays (since C# 1.0)
2.    Covariance and Contravariance in delegates, also known as “method group variance” (since C# 2.0)
3.    Variance for generic type parameters in interfaces and delegates (since C# 4.0)

Let’s learn examples for each of them

Covariance in Arrays:

class Program

    {

        static void Main(string[] args)

        {

            //Covariance in array

            //I can directly assign more derived type to its one of the base types in hierarchy

            object[] obj = new string[10];

            obj[0] = "Abhishek";

            Console.WriteLine(obj[0]);

            //Not 100% safe, check following line which will compile but throw runtime exception
            //Exception: ArrayTypeMismatchException
            obj[1] = new object();
            Console.Read();
        }

Covariance and Contravariance in Delegates:

class Program

    {

        static void Main(string[] args)

        {

            //Covariance in delegate

            Func<object> myFuncExForCovariance = GetMyName;

            Console.WriteLine(myFuncExForCovariance());

            //Contravariance in delegate

            Action<string> myActionExForContravariance = PrintMyName;

            myActionExForContravariance("Abhishek");

            Console.Read();

        }



        static String GetMyName()

        {

            return "Abhishek";

        }

        static void PrintMyName(object s)

        {
           Console.WriteLine(s);
        }

    }

Does this look scary? How to find when it is supporting covariance or Contravariance? Actually .Net 4 provides you syntax for that. If type is “out” it means it is Contravariance and you can assign less derived type. If type is “in” it means it is covariance and you can assign more derived type. So if you look at the metadata of the delegates used above, you would be able to understand it clearly

    // Type parameters:

    //   T:

    //     The type of the parameter of the method that this delegate encapsulates.This

    //     type parameter is contravariant. That is, you can use either the type you

    //     specified or any type that is less derived. For more information about covariance

    //     and contravariance, see Covariance and Contravariance in Generics.

    public delegate void Action<in T>(T obj);


// Type parameters:

    //   TResult:

    //     The type of the return value of the method that this delegate encapsulates.This

    //     type parameter is covariant. That is, you can use either the type you specified

    //     or any type that is more derived. For more information about covariance and

    //     contravariance, see Covariance and Contravariance in Generics.
 public delegate TResult Func<out TResult>();


Infact you can write your own delegates and interfaces using the same syntax of variance.

class Program

    {

        public delegate TResult MyCustomDelegate<in Tin1, in Tin2, out TResult>(Tin1 input1, Tin2 input2);


        static void Main(string[] args)

        {

            MyCustomDelegate<DogDogobject> mydel = TestMyDelegateWithVariance;

            Console.WriteLine(mydel(new Dog(), new Dog()));

            Console.Read();

        }

        public static string TestMyDelegateWithVariance(Animal i, Animal j)
        {

            return (i.legs + j.legs).ToString();
        }
    }

    class Animal
    {
      
        public int legs { getset; }
        public Animal()
        {
            legs = 2;
        }
    }

    class Dog : Animal
    {
        public Dog()
        {
            legs = 4;
        }
    }

This will print 4 + 4 = 8 as expected.

The point to note is that covariance and Contravariance is meant for reference types only, the concept is not applicable on value types.

//will run fine

IEnumerable<object> oRefToRef = new List<String>();

//will throw a compile time error, need explicit typecasting

IEnumerable<object> o = new List<int>();
  
Variance on generic interfaces:

class Program
    {
        static void Main(string[] args)
        {
            //The following conversion will work fine due to variance
            ITestVariance<DogAnimal> myTestVariance = new ConcreteTestVariance<AnimalDog>();

            //Following will demand for explicit conversion since wrong usage of variance constraint
            ITestVariance<DogAnimal> myTestVariance = new ConcreteTestVariance<PamellianDogAnimal>();
   
            Console.Read();
        }
    }

    interface ITestVariance<in T, out T1>
    {
    }
    //following interface is invariant in nature since it does not specify
    //in/out paramater

    interface ITestInVariance : ITestVariance
    {
    }

    //following interface is variant in nature 
    interface IDerTestVariance<in T, out T1> : ITestVariance
    {
    }

    //classes and structs are always invariant (remember only delegates and interfaces
    //of generic types can support variance)
    class ConcreteTestVariance : ITestVariance where T1 : T
    {
    }

    class Animal
    {
        public int legs { getset; }
        public Animal()
        {
            legs = 2;
        }
    }

    class Dog : Animal
    {
        public Dog()
        {
            legs = 4;
        }
    }

    class PamellianDog : Dog { }


The following interfaces from framework already support variance


The following delegates from framework has inbuilt variance support

  • Action delegates from the System namespace, for example, Action and Action (T, T1, T2, and so on are contravariant)
  • Func delegates from the System namespace, for example, Func and Func (TResult is covariant; T, T1, T2, and so on are contravariant)
  • Predicate (T is contravariant)
  • Comparison (T is contravariant)
  • Converter (TInput is contravariant; TOutput is covariant.)








No comments:

Post a Comment