C# 4.0: Covariance And Contravariance In Generics
Posted
by Paulo Morgado
on ASP.net Weblogs
See other posts from ASP.net Weblogs
or by Paulo Morgado
Published on Tue, 13 Apr 2010 01:25:58 GMT
Indexed on
2010/04/13
1:33 UTC
Read the original article
Hit count: 559
C# 4.0 (and .NET 4.0) introduced covariance and contravariance to generic interfaces and delegates. But what is this variance thing?
According to Wikipedia, in multilinear algebra and tensor analysis, covariance and contravariance describe how the quantitative description of certain geometrical or physical entities changes when passing from one coordinate system to another.(*)
But what does this have to do with C# or .NET?
In type theory, a the type T is greater (>) than type S if S is a subtype (derives from) T, which means that there is a quantitative description for types in a type hierarchy.
So, how does covariance and contravariance apply to C# (and .NET) generic types?
In C# (and .NET), variance applies to generic type parameters and not to the resulting generic type. A generic type parameter is:
- covariant if the ordering of the generic types follows the ordering of the generic type parameters: Generic<T> = Generic<S> for T = S.
- contravariant if the ordering of the generic types is reversed from the ordering of the generic type parameters: Generic<T> = Generic<S> for T = S.
- invariant if neither of the above apply.
If this definition is applied to arrays, we can see that arrays have always been covariant because this is valid code:
object[] objectArray = new string[] { "string 1", "string 2" }; objectArray[0] = "string 3"; objectArray[1] = new object();
However, when we try to run this code, the second assignment will throw an ArrayTypeMismatchException. Although the compiler was fooled into thinking this was valid code because an object is being assigned to an element of an array of object, at run time, there is always a type check to guarantee that the runtime type of the definition of the elements of the array is greater or equal to the instance being assigned to the element. In the above example, because the runtime type of the array is array of string, the first assignment of array elements is valid because string = string and the second is invalid because string = object.
This leads to the conclusion that, although arrays have always been covariant, they are not safely covariant – code that compiles is not guaranteed to run without errors.
In C#, the way to define that a generic type parameter as covariant is using the out generic modifier:
public interface IEnumerable<out T> { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> { T Current { get; } bool MoveNext(); }
Notice the convenient use the pre-existing out keyword. Besides the benefit of not having to remember a new hypothetic covariant keyword, out is easier to remember because it defines that the generic type parameter can only appear in output positions — read-only properties and method return values.
In a similar way, the way to define a type parameter as contravariant is using the in generic modifier:
public interface IComparer<in T> { int Compare(T x, T y); }
Once again, the use of the pre-existing in keyword makes it easier to remember that the generic type parameter can only be used in input positions — write-only properties and method non ref and non out parameters.
Because covariance and contravariance apply only to the generic type parameters, a generic type definition can have both covariant and contravariant generic type parameters in its definition:
public delegate TResult Func<in T, out TResult>(T arg);
A generic type parameter that is not marked covariant (out) or contravariant (in) is invariant.
All the types in the .NET Framework where variance could be applied to its generic type parameters have been modified to take advantage of this new feature.
In summary, the rules for variance in C# (and .NET) are:
-
Variance in type parameters are restricted to generic interface and generic delegate types.
-
A generic interface or generic delegate type can have both covariant and contravariant type parameters.
-
Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
-
Variance does not apply to delegate combination. That is, given two delegates of types Action<Derived> and Action<Base>, you cannot combine the second delegate with the first although the result would be type safe. Variance allows the second delegate to be assigned to a variable of type Action<Derived>, but delegates can combine only if their types match exactly.
If you want to learn more about variance in C# (and .NET), you can always read:
Note: Because variance is a feature of .NET 4.0 and not only of C# 4.0, all this also applies to Visual Basic 10.
© ASP.net Weblogs or respective owner