Covariance, Invariance and Contravariance - What are they?
I'm 💯 sure that you might have never heard of them, yet you have been using these concepts in your code everyday.
Covariance, Invariance and Contravariance define the behaviour and ability to assign and use a derived type in place of a more generic type.
Mathematically, let's say B is a base type and D is a derived type of B (B > D)
For a transformation applied f on both B and D,
f is covariant when f(D) can be substituted for f(B)
f is contravariant when f(B) can be substituted for f(D) [reverse of covariant]
f is invariant when f(B) and f(D) are completely different and not substitutable.
For example, let's say there is a base type Figure and a derived type Square [Square extends Figure]
Let these types be used for a collection IEnumerable.
Covariance is as below -
// covariance
IEnumerable<Square> sq = new List<Square>();
IEnumerable<Figure> f = sq;
// No compilation issues - assignable
On the other hand, this is not possible -
// contravariance
IEnumerable<Figure> f = new List<Figure>();
IEnumerable<Square> sq = f;
// Compilation error - not assignable
// invariance
List<Square> f = new List<Square>();
List<Figure> sq = f;
// Compilation error - not assignable
Hence you can say that IEnumerable and it's subtypes are covariant on type parameters but not contravariant or invariant.
They are mentioned IEnumerable<out T>. Other examples are - IEnumerable<T>, IEnumerator<T>, IQueryable<T>, and IGrouping<TKey,TElement>
Type parameters in covariants are used only for the return types of the members.
Some other generic types have contravariant type parameters - IComparer<T>, IComparable<T>, and IEqualityComparer<T>
Type parameters in contravariants are used only as parameter types in the members of the interfaces.
Action types are not covariant but contravariant on type parameters. You mention the contravariance as Action<in T>
// contravariance
Action<Figure> b = (f) => { Console.WriteLine(f.GetType().Name); };
Action<Square> d = b;
// no compilation error - assignable
d(new Square());
Func types are contravariant on type parameters and covariant on return types. Hence you write Func<in T,out TResult>