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> { }