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 aa
cannot 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
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
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.