Covariance and Contravariance in Generics

In probability theory and statistics, covariance is a measure of how much two random variables change together. If the greater values of one variable mainly correspond with the greater values of the other variable, and the same holds for the smaller values, i.e., the variables tend to show similar behavior, the covariance is positive.In the opposite case, when the greater values of one variable mainly correspond to the smaller values of the other, i.e., the variables tend to show opposite behavior, the covariance is negative. The sign of the covariance therefore shows the tendency in the linear relationship between the variables.

CLR team might have found my assumption quite viable and it got shape in .NET 4.0 as “Covariance and Contravariance in Generics”.

Variance is a special case of the covariance when the two variables are identical

Covariance and contravariance of generic type parameters enable to use constructed generic types whose type arguments are more derived (covariance) or less derived (contravariance) than a target constructed type.

In general, a covariant type parameter can be used as the return type of a generic delegate/interface, and contravariant type parameters can be used as input parameter types.

IEnumerable<T>, IEnumerator<T>—– Covariance

Comparison<T>, Action<T>, IComparer<T> contravariance

Func<T, TResult>, Converter<TInput, TOutput>— Encapsulates a method that has one parameter and returns a value of the type specified by the TResult parameter.it is Covariance and contravariance support.

Func<string, string> convertMethod = UppercaseString;(it
      string name = "Dakota";
      // Use delegate instance to call UppercaseString method
      Console.WriteLine(convertMethod(name));

 

IEnumerable< BaseVO > lstBaseVO = new List< FlightVO >();The above statement is valid because, in .NET 4.0, List<T> : IEnumerable<out T> where type ‘T’ is marked as covariant.
  • A covariant type parameter is marked with the out keyword. we cannot use a covariant type parameter as a generic type constraint for interface methods. E.g., T Display<out T>(); is an invalid statement.
  • A contravariant type parameter is marked with the in keyword. we can use a contravariant type parameter as a generic type constraint for an interface method.

 

Generic delegates enable type-safe callbacks without the need to create multiple delegate classes

where T : class The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
where T : new() The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
where T : <base class name> The type argument must be or derive from the specified base class.
where T : <interface name> The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.
public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

For a generic class Node<T>, client code can reference the class either by specifying a type argument, to create a closed constructed type (Node<int>). Alternatively, it can leave the type parameter unspecified, for example when you specify a generic base class, to create an open constructed type (Node<T>). Generic classes can inherit from concrete, closed constructed, or open constructed base classes:

class BaseNode { }
class BaseNodeGeneric<T> { }
//An unbound type has no type arguments specified(collection class)

// concrete type
class NodeConcrete<T> : BaseNode { }

//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }

//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }

Generic Delegates

A delegate can define its own type parameters. Code that references the generic delegate can specify the type argument to create a closed constructed type, just like when instantiating a generic class or calling a generic method.
public delegate void Del<T>(T item);
public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);


Contravariance

Contravariance works the other way than covariance. Let’s say we have a method that creates some cats and compares them using a provided IComparer<Cat> object. In a more realistic example, the method might, for example, sort the cats:

void CompareCats(IComparer<Cat> comparer) {
  var cat1 = new Cat("Otto");
  var cat2 = new Cat("Troublemaker");
  if (comparer.Compare(cat2, cat1) > 0) 
    Console.WriteLine("Troublemaker wins!");
}

The comparer object takes cats as arguments, but it never returns a cat as the result. You could say that it is a write-only in the way in which it uses the generic type parameter. Now, thanks to contravariance, we can create a comparer that can compare animals and use it as an argument to CompareCats:

IComparator<Animal> compareAnimals = new AnimalSizeComparator();
CompareCats(compareAnimals);

The compiler accepts this code because the IComparer interface is contravariant and its generic type parameter is marked with the in annotation. When you run the program, it also makes sense. The compareAnimals object that we created knows how to compare animals and so it can certainly also compare two cats. A problem would be if we could read a Cat from IComparer<Cat> (because we couldn’t get Cat from IComparer<Animal>!), but that is not possible, because IComparer is write-only.

Leave a Reply