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());