Invariance, Covariance and Contravariance

Polymorphism

We can assign a derived instance to a base reference, this is trivial šŸ™‚

Base ref1 = new Derived();

It also work with generic classes, as long as the type parameter is the same:

GenericBase<Base> ref2 = new GenericDerived<Base>();
GenericBase<Derived> ref3 = new GenericDerived<Derived>();

Invariance

But we have an error if we try to apply the same principle to the type parameter:

GenericBase<Base> ref4 = new GenericBase<Derived>();  // <= This is not allowed!

The reason is that for a type parameter, by default, we don’t have any polymorphic behavior. We can only use the type originally specified. In this case we say that the type parameter is Invariant.

The ability to have a polymorphic behavior on a type parameter is possible throught the concept of Covariance (and Contravariance, which is the opposite). If a type parameter is either Covariant or Contravariant we say it’s Variant. In .Net Variance in only supported for interfaces and delegates.


Covariance

Is the ability to assign a derived type to a base type parameter. Can be specified by adding the “out” keyword to the type parameter definition.

ICovariantInterface <Base> ref5 = new CovariantBaseClass<Derived>();
ICovariantInterface<Base> ref6 = new CovariantDerivedClass<Derived>();

An example of Covariance in .Net is the IEnumerable interface:

IEnumerable<Base> ref7 = new List<Derived>();

A Covariant type parameter can only be used as return type (hence the “out”).


Contravariance

Is the opposite of the Covariance, it allows to assign a base type to a derived type parameter. Can be specified by adding the “in” keyword to the type parameter definition.

IContravariantInterface<Derived> ref8 = new ContravariantBaseClass<Base>();
IContravariantInterface<Derived> ref9 = new ContravariantDerivedClass<Base>();

An example of Contravariance in .Net is the Action delegate:

Action<Base> action = (Base p) => { };
Action<Derived> ref10 = action;

A Contravariant type parameter can only be used as argument type (hence the “in”). Is used for instance when we want to pass an action that works for the base type to a method that accepts an action that works for the derived type parameter.

ApplyActionToDerived(action, new Derived());

Given:

void ApplyActionToDerived(Action<Derived> action, Derived target)
{
    action(target);
}

Here are the definitions of the classes and interfaces used in this examples:

public class Base
{ }

public class Derived : Base
{ }

public class GenericBase<T>
{ }

public class GenericDerived<T> : GenericBase<T>
{ }

public interface ICovariantInterface<out T>
{
    T ValidCovariantMethod();
    bool InvalidCovariantMethod(T argument); // <= This is not allowed!
}

public class CovariantBaseClass<T> : ICovariantInterface<T>
{
    public T ValidCovariantMethod()
    {
        throw new NotImplementedException();
    }
    public bool InvalidCovariantMethod(T argument)
    {
        throw new NotImplementedException();
    }
}

public class CovariantDerivedClass<T> : CovariantBaseClass<T>
{ }

public interface IContravariantInterface<in T>
{
    bool ValidContravariantMethod(T argument);
    T InvalidContravariantMethod(); // <= This is not allowed!
}

public class ContravariantBaseClass<T> : IContravariantInterface<T>
{
    public bool ValidContravariantMethod(T argument)
    {
        throw new NotImplementedException();
    }
    public T InvalidContravariantMethod()
    {
        throw new NotImplementedException();
    }
}

public class ContravariantDerivedClass<T> : ContravariantBaseClass<T>
{ }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s