Understanding Covariance & Contravariance

What is Covariance & Contravariance?

Covariance – Enables you to use a more derived type than originally specified.
You can assign an instance of IEnumerable<Derived> to a variable of type IEnumerable<Base>.

Covariant type parameters enable you to make assignments that look much like ordinary Polymorphism, as shown in the following code.

IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;

Contravariance – Enables you to use a more generic (less derived) type than originally specified.
You can assign an instance of IEnumerable<Base> to a variable of type IEnumerable<Derived>.

Action<Base> b = (target) => {Console.WriteLine(target.GetType().Name);};
Action<Derived> d = b;
d(new Derived());

Invariance – Means that you can use only the type originally specified; so an invariant generic type parameter is neither covariant nor contravariant.

You cannot assign an instance of IEnumerable<Base> to a variable of type IEnumerable<Derived> or vice versa.

Arrays/Delegates/Generics

Arrays – Are covariant

Bear[] bears = new Bear[3];
Animal[] animals = bears; // OK

The downside of this reusability is that element assignments can fail at runtime:

animals[ 0 ] = new Camel(); // Runtime error

Delegates – Allows for methods to have less specific input parameters (covariance) and more specific return types (contravariance)

Covariance – Parameter Compatibility

  • A method target can define a parameter that is more generic than described by the delegate OR
  • A delegate can have more specific parameter types than its methods target
delegate void StringAction (string s);

static void Main ()
{
   StringAction sa = new StringAction (ActOnObject);
   sa ( "hello" );
}
static void ActOnObject (object o)
{
   Console.WriteLine (o); // hello
}

Contravariance – Return Type Compatibility

  • A method target can return a more specific type than described by the delegate OR
  • A delegate can define a return type that is more generic than the method signature
delegate object ObjectRetriever();
static void Main()
{
    ObjectRetriever o = new ObjectRetriever (RetriveString);
    object result = o();
    Console.WriteLine (result);      // hello
}

static string RetriveString() { return "hello"; }

Generics

Covariance – Allowed for parameters marked with “out” modifier

// Covariant interface.
interface ICovariant<out R> { }

// Extending covariant interface.
interface IExtCovariant<out R> : ICovariant<R> { }

// Implementing covariant interface.
class Sample<R> : ICovariant<R> { }

class Program
{
    static void Test()
    {
        ICovariant<Object> iobj = new Sample<Object>();
        ICovariant<String> istr = new Sample<String>();

        // You can assign istr to iobj because
        // the ICovariant interface is covariant.
        iobj = istr;
    }
}

Contravariance – Allowed for parameters marked with the “in” modifier

// Contravariant interface.
interface IContravariant<in A> { }

// Extending contravariant interface.
interface IExtContravariant<in A> : IContravariant<A> { }

// Implementing contravariant interface.
class Sample<A> : IContravariant<A> { }

class Program
{
    static void Test()
    {
        IContravariant<Object> iobj = new Sample<Object>();
        IContravariant<String> istr = new Sample<String>();

        // You can assign iobj to istr because
        // the IContravariant interface is contravariant.
        istr = iobj;
    }
}

Invariance – Means that you can use only the type originally specified

Generic classes are not covariant to ensure static type safety

class Animal {}

class Bear : Animal {}

class Camel : Animal {}

public class Stack<T>  // A simple Stack implementation
{
      int position;
      T[] data = new T[100];

      public void Push (T obj) { data [ position ++ ] = obj; }
      public T Pop() { return data [ -- position ]; }
}

The following fails to compile:

Stack<Bear> bears = new Stack<Bear>();
Stack<Animal> animals = bears; // Compile-time error

That restriction prevents the possibility of runtime failure with the following code:

animals.Push ( new Camel());

Leave a Reply

Your email address will not be published. Required fields are marked *