Last active
December 9, 2017 20:46
-
-
Save asarnaout/40e9ae739f9eb10e5f0d16b5e50ee3df to your computer and use it in GitHub Desktop.
Covariance & Contravariance in .NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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>(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConcreteContravariantType<T> : IContravariantType<T> | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConcreteCovariantType<T> : ICovariantType<T> | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ConcreteInvariantType<T> : IInvariantType<T> | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IContravariantType<in T> //Contravariant types are declared using the 'in' keyword | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface ICovariantType<out T> //Covariant types are declared using the 'out' keyword | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IInvariantType<T> | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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