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

Dependency Injection

What is Dependency Injection?

This is a pattern used for decoupling components and layers in the system. The pattern is implemented through injecting dependencies into a component when it is constructed. These dependences are usually provided as interfaces for further decoupling and to support testability.

Pure Dependency Injection (Poor man’s DI)

Pure Dependency Injection uses constructor or property Injection where lower level components are passed using constructors or properties.

DI container

Simplifies the creation of dependencies and also provides benefits like Interception (for Aspect oriented programming) for cross-cutting concerns like logging, caching security etc.

DI style Advantages Disadvantages
Poor Man’s DI Easy to learn
Strongly typed
Rapid Feedback
High maintenance
DI Container Low maintenance

Convention/xml registration

Weakly typed
Harder to learn
Slower feedback

Without DI container

var svc = new ShippingService(new ProductLocator(), new PricingService(), new InventoryService(), new TrackingRepository(new ConfigProvider()), new
 Logger(new EmailLogger(new ConfigProvider())));

With DI Container (Autowiring)

var svc = IoC.Resolve();

Comparing DI Containers

Autofac DryIoc SimpleInjector Unity
Configuration XML/Auto Auto XML/Auto
Custom Lifetimes Yes Yes Yes
Interception Yes Yes Yes
Auto Diagnostics Yes
Speed Average Fast Fast Average

Container compatibility for generics vary especially for the following scenarios and a more detailed analysis is needed with regards to capabilities required by our system

  • Open Generics
  • Covariance & Contravariance
  • Optional parameters
  • Func & Lazy support