Assignments According to Covariance and Contravariance in Generics






4.78/5 (4 votes)
Just a quick sample to clarify what we can do and what we can't
Introduction
Speaking with some younger colleagues, I realized that often the concepts of covariance and contravariance are not completely assimilated.
Referring to the official documentation for the correct explanation, this tip is only meant to facilitate understanding of these concepts through simple examples in a few lines of code, indeed I remember that I really understood these concepts only after I started to read and write some code about it.
How to Manipulate Generic Covariant and Contravariant Delegates (and Interfaces)
For the following examples, we just need to declare two empty classes: Base
and Derived
(where Derived
implements Base
).
class Base { }
class Derived : Base { }
I considered assignments for delegates with:
- a covariant type parameter:
Func<out TResult>
- a contravariant type parameter:
Action<in T>
- both covariant and contravariant type parameters:
Func<in T, out TResult>
Please note that all samples are with delegates but for interfaces is the same thing.
namespace Bagnasco.Samples.Variance
{
using System;
class Base { }
class Derived : Base { }
/// <summary>
/// Sample class just for clarifying variance concepts
/// (see: http://msdn.microsoft.com/it-it/library/dd799517(v=vs.110).aspx)
/// </summary>
class VarianceAssignmentsSample
{
public void VarianceSample()
{
Base baseInstance = new Base();
Derived derivedInstance = new Derived();
//Ok (ordinary polymorphism)
baseInstance = derivedInstance;
//Type parameters signature: <out TResult>
Func<Base> funcThatReturnsBase = () => baseInstance;
Func<Derived> funcThatReturnsDerived = () => derivedInstance;
//Ok, mutch like ordinary polymorphism (TResult is covariant)
funcThatReturnsBase = funcThatReturnsDerived;
//Not allowed (TResult is more derived in funcThatReturnsDerived and is not contravariant)
//funcThatReturnsDerived = funcThatReturnsBase;
//Type parameters signature: <in T>
Action<Base> actionThatTakesBase = _ => { };
Action<Derived> actionThatTakesDerived = _ => { };
//Ok (T is contravariant)
actionThatTakesDerived = actionThatTakesBase;
//Not allowed (T is not covariant and is less derived in actionThatTakesBase)
//actionThatTakesBase = actionThatTakesDerived
//Type parameters signature: <in T, out TResult>
Func<Base, Base> functionThatTakesBaseAndReturnsBase = _ => baseInstance;
Func<Base, Derived> functionThatTakesBaseAndReturnsDerived = _ => derivedInstance;
Func<Derived, Base> functionThatTakesDerivedAndReturnsBase = _ => baseInstance;
Func<Derived, Derived> functionThatTakesDerivedAndReturnsDerived = _ => derivedInstance;
//Ok (TResult is covariant and in functionThatTakesBaseAndReturnsBase is less derived)
functionThatTakesBaseAndReturnsBase = functionThatTakesBaseAndReturnsDerived;
//Not allowed
//(T is not covariant and in functionThatTakesBaseAndReturnsBase is less derived)
//functionThatTakesBaseAndReturnsBase = functionThatTakesDerivedAndReturnsBase;
//functionThatTakesBaseAndReturnsBase = functionThatTakesDerivedAndReturnsDerived;
//Not allowed
//(TResult is covariant and in functionThatTakesBaseAndReturnsDerived is more derived)
//functionThatTakesBaseAndReturnsDerived = functionThatTakesBaseAndReturnsBase;
//Not allowed
//(T is contravariant and in functionThatTakesBaseAndReturnsDerived is less derived
//functionThatTakesBaseAndReturnsDerived = functionThatTakesDerivedAndReturnsDerived;
//Not allowed
//(T is contravariant and in functionThatTakesBaseAndReturnsDerived is less derived,
// TResult is covariant and in functionThatTakesBaseAndReturnsDerived is more derived)
//functionThatTakesBaseAndReturnsDerived = functionThatTakesDerivedAndReturnsBase;
//All assignments are allowed
//(T is contravariant and in functionThatTakesDerivedAndReturnsBase is more derived,
// TResult is covariant and in functionThatTakesDerivedAndReturnsBase is less derived)
functionThatTakesDerivedAndReturnsBase = functionThatTakesBaseAndReturnsBase;
functionThatTakesDerivedAndReturnsBase = functionThatTakesBaseAndReturnsDerived;
functionThatTakesDerivedAndReturnsBase = functionThatTakesDerivedAndReturnsDerived;
//Ok (T is contravariant and in functionThatTakesDerivedAndReturnsDerived is more derived)
functionThatTakesDerivedAndReturnsDerived = functionThatTakesBaseAndReturnsDerived;
//Not allowed
//(TResult is covariant and in functionThatTakesDerivedAndReturnsDerived is more derived)
//functionThatTakesDerivedAndReturnsDerived = functionThatTakesBaseAndReturnsBase;
//functionThatTakesDerivedAndReturnsDerived = functionThatTakesDerivedAndReturnsBase;
}
}
}
I hope this will help to better understand covariance and contravariance in Generics.