Skip to content

Instantly share code, notes, and snippets.

@asarnaout
Last active December 9, 2017 20:46
Show Gist options
  • Save asarnaout/40e9ae739f9eb10e5f0d16b5e50ee3df to your computer and use it in GitHub Desktop.
Save asarnaout/40e9ae739f9eb10e5f0d16b5e50ee3df to your computer and use it in GitHub Desktop.
Covariance & Contravariance in .NET
/*
* In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments.
* Covariance preserves assignment compatibility and contravariance reverses it.
*/
/************************************************************* Covariance *************************************************************/
/*
* Covariance for enables implicit conversion of a more derived generic type to a less derived generic type.
*
* For example, the IEnumerable interface is Covariant, and thus we are allowed to convert a List<string> (a more derived generic type) to
* an IEnumerable<object> (a less derived generic type).
*/
IEnumerable <object> enumerable1 = new List<string> {"a", "b", "c"};
/*
* However since IEnumerable is covariant, then we could not do the following:
*/
IEnumerable <string> enumerable1 = new List<object> {"a", "b", "c"};
/*
* You set a generic interface to be covariant by adding the 'out' keyword next to the type parameter
*/
ICovariantType<object> covariant = new ConcreteCovariantType<string>();
/*********************************************************** Contravariance ***********************************************************/
/*
* On the contrary, contravariance enables implicit conversion of a less derived generic type to a more derived generic type.
*
* For example, the Action delegate is Contravariant, and thus we are allowed to convert a method accepting an object parameter to a
* delegate accepting a string parameter
*/
Action<Manager> act = Methods.SomeMethodTakingAnObjectParameter;
/*
* You set a generic interface to be contravariant by adding the 'in' keyword next to the type parameter
*/
IContravariantType<string> contraVariant = new ConcreteContravariantType<object>();
/************************************************************* Invariance *************************************************************/
/*
* Invariant types (Generic Interfaces/delegates created without in/out type parameters) don't allow conversions between generic types.
*
* For example, the following code will not compile:
*/
IInvariantType<object> invariant = new ConcreteInvariantType<string>();
/*************************************************** Covariant & Contravariant Delegates ***************************************************/
/*
* So far, we've investigated how to use covariance and contravariance with interfaces. To understand how delegates could be
* covariant/contravariant, we should inspect the 'Func' type's internal implementation:
*/
namespace System
{
public delegate TResult Func<in T, out TResult>(T arg);
}
/*
* This implementation of Func takes a parameter of type T and returns a result of type TResult, adding that the generic type parameter T
* is contravariant while TResult is covariant.
*
* So why was this distinction introduced?
*
* Since Func is covariant on its output type parameters, therefore any function returning a type 'TDerived' could be assigned to a Func
* returning 'TBase' (since 'TDerived' could be converted to 'TBase') therefore type safety is maintained.
*
* Meanwhile, Func is contravariant on its input type parameters, therefore a function accepting 'TBase' as an input type argument could
* be assigned to a Func accepting 'TDerived' as an input type argument therefore the Func would always be called with arguments of type
* 'TDerived' that could be converted to 'TBase', and thus, type safety is still maintained.
*
* In the following example, we assign a method accepting an object parameter and returning a string parameter to Func<string, object>,
* implying that this delegate could only be passed a string parameter (that is safely casted to an object while calling the referenced
* function), and always returns an object (after safely casting the referenced method's string output to an object).
*/
Func<string, object> myDelegate = Methods.MethodTakingAnObjectAndReturningAString;
object invocationResult = myDelegate("Input Parameter");
/********************************************** Covariance/Contravariance & Value Types **********************************************/
/*
* It is worthy to note that covariance/contravariance principles don't work with value types, implying that the following code will
* cause an error:
*/
IEnumerable<object> myList = new List<int>();
public class ConcreteContravariantType<T> : IContravariantType<T>
{
}
public class ConcreteCovariantType<T> : ICovariantType<T>
{
}
public class ConcreteInvariantType<T> : IInvariantType<T>
{
}
public interface IContravariantType<in T> //Contravariant types are declared using the 'in' keyword
{
}
public interface ICovariantType<out T> //Covariant types are declared using the 'out' keyword
{
}
public interface IInvariantType<T>
{
}
public class Methods {
public static void SomeMethodTakingAnObjectParameter(object param)
{
}
public static string MethodTakingAnObjectAndReturningAString(object param)
{
return string.Empty;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment