Working with Co-Variance and Contra-Variance in .NET 4.0
By | June 29, 2011

If you literally think of variance, you would say variance is a concept in which if one data varies at a certain time, the other one will vary automatically. And rightly so. In case of .NET, variance comes just in such way. Lets talk about why we need variance or Co-variance in .NET.

Before we do, have you ever did something like this ?

IA a = new A ();
List<IA> aa = new List<A>(enumerables);

The two likes will not compile before .NET 4.0. It will throw InvalidCastException as it don’t know if List is actually a List for each enumerable.

Why this is so?
Say I do :

aa.Add(new B()); //Considering B implements IA

This will produce problems as aacannot be cast to List anymore. To address such situations .NET 4.0 introduces Co-variance and contra-variance.

Lets take another example to clear this :

    public interface IPeople
    {
        string GetName();
    }

    public class RuralPeople : IPeople
    {

        #region IPeople Members

        public string GetName()
        {
            return "RuralPeople";
        }

        #endregion
    }

    public class UrbanPeople : IPeople
    {

        #region IPeople Members

        public string GetName()
        {
            return "UrbaPeople";
        }

        #endregion
    }

Here the two class RuralPeople and UrbanPeople both implements from IPeople. Now lts define a writer which writes collection of names of these classes :

interface IWriter<T>
    {
        void WriteNames(IEnumerable<T> ts);
    }

 public class Writer<T> : IWriter<T> where T : IPeople
 {
        public void WriteNames(IEnumerable<T> ts)
        {
            foreach (var t in ts)
            {
                Console.WriteLine(t.GetName());
            }
        }
    }  

Now if I say :

List<RuralPeople> ruralist = new List<RuralPeople> { new RuralPeople() };
IWriter<IPeople> writer = new Writer<IPeople>();
writer.WriteNames(ruralist);  

This will produce a compiler error in .NET 3.0. This is because of the fact that IPeople cannot write names from List. In .NET 4.0 we solve this issue by putting out parameter in IEnumerable. Hence this works fine in .NET 4.0.

public interface IEnuerable<out T> : IEnumerable
{
}

The out keyword in T makes T covariant which means you can pass any value to T which is derived from T. In our case IPeople. Hence we can now pass IEnumerable of RuralPeople and IEnumerable or UrbanPeople easily to IPeople and it will hold its variance completely.

The generic out parameter restricts the class to only return Ts to outside environment. Hence T can occur only as output parameter. Hence you cannot pass a T to the class from outside. Thus this ensure that the List is actually either List or List and your add method does not allow to pass IPeople anymore.

Contra-variance
Now if we say want to use variance but still need to create instance of T inside the class. Contra-variance deals with such situations. You can annotate the generic parameter with “in” keyword and it will ensure that T can be used only in input positions.

  interface IWriter<in T>
    {
        void WriteNames(IEnumerable<T> ts);
    }

This will make the following lines legal :

List<RuralPeople> rurals = new List<RuralPeople> { new RuralPeople() };
List<UrbanPeople> urbans = new List<UrbanPeople> { new UrbanPeople() };
IWriter<IPeople> peoples = new Writer<IPeople>();
IWriter<RuralPeople> ruralwriter = peoples;
IWriter<UrbanPeople> urbanwriter = peoples;
ruralwriter.WriteNames(rurals);
urbanwriter.WriteNames(urbans);  

Hence you can say by RuralPeople and UrbanPeople is contravariant to IPeople and hence you can assign instance of Writer of IPeople to IWriter of RuralPeople or UrbanPeople easily and it will process both the WriteNames appropriately.

This is not the end of this. Generic variance is also spread over delegates. In similar way you can use Generic Co-variance and Contra-variance for delegates too.
I hope this clears the concept
Happy coding.