Covariance and contravariance in generics
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
When you're referring to a type system, covariance, contravariance, and invariance have the following definitions. The examples assume a base class named Base
and a derived class named Derived
.
Covariance
Enables you to use a more derived type than originally specified.
You can assign an instance of
IEnumerable<Derived>
to a variable of typeIEnumerable<Base>
.Contravariance
Enables you to use a more generic (less derived) type than originally specified.
You can assign an instance of
Action<Base>
to a variable of typeAction<Derived>
.Invariance
Means that you can use only the type originally specified. An invariant generic type parameter is neither covariant nor contravariant.
You cannot assign an instance of
List<Base>
to a variable of typeList<Derived>
or vice versa.
Covariant type parameters enable you to make assignments that look much like ordinary Polymorphism, as shown in the following code.
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
The List<T> class implements the IEnumerable<T> interface, so List<Derived>
(List(Of Derived)
in Visual Basic) implements IEnumerable<Derived>
. The covariant type parameter does the rest.
Contravariance, on the other hand, seems counterintuitive. The following example creates a delegate of type Action<Base>
(Action(Of Base)
in Visual Basic), and then assigns that delegate to a variable of type Action<Derived>
.
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())
This seems backward, but it is type-safe code that compiles and runs. The lambda expression matches the delegate it's assigned to, so it defines a method that takes one parameter of type Base
and that has no return value. The resulting delegate can be assigned to a variable of type Action<Derived>
because the type parameter T
of the Action<T> delegate is contravariant. The code is type-safe because T
specifies a parameter type. When the delegate of type Action<Base>
is invoked as if it were a delegate of type Action<Derived>
, its argument must be of type Derived
. This argument can always be passed safely to the underlying method, because the method's parameter is of type Base
.
In general, a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types. For an interface, covariant type parameters can be used as the return types of the interface's methods, and contravariant type parameters can be used as the parameter types of the interface's methods.
Covariance and contravariance are collectively referred to as variance. A generic type parameter that is not marked covariant or contravariant is referred to as invariant. A brief summary of facts about variance in the common language runtime:
Variant 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>
andAction<Base>
(Action(Of Derived)
andAction(Of Base)
in Visual Basic), 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 typeAction<Derived>
, but delegates can combine only if their types match exactly.Starting in C# 9, covariant return types are supported. An overriding method can declare a more derived return type the method it overrides, and an overriding, read-only property can declare a more derived type.
Generic interfaces with covariant type parameters
Several generic interfaces have covariant type parameters, for example, IEnumerable<T>, IEnumerator<T>, IQueryable<T>, and IGrouping<TKey,TElement>. All the type parameters of these interfaces are covariant, so the type parameters are used only for the return types of the members.
The following example illustrates covariant type parameters. The example defines two types: Base
has a static method named PrintBases
that takes an IEnumerable<Base>
(IEnumerable(Of Base)
in Visual Basic) and prints the elements. Derived
inherits from Base
. The example creates an empty List<Derived>
(List(Of Derived)
in Visual Basic) and demonstrates that this type can be passed to PrintBases
and assigned to a variable of type IEnumerable<Base>
without casting. List<T> implements IEnumerable<T>, which has a single covariant type parameter. The covariant type parameter is the reason why an instance of IEnumerable<Derived>
can be used instead of IEnumerable<Base>
.
using System;
using System.Collections.Generic;
class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}
class Derived : Base
{
public static void Main()
{
List<Derived> dlist = new List<Derived>();
Derived.PrintBases(dlist);
IEnumerable<Base> bIEnum = dlist;
}
}
Imports System.Collections.Generic
Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class
Class Derived
Inherits Base
Shared Sub Main()
Dim dlist As New List(Of Derived)()
Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class
Generic interfaces with contravariant type parameters
Several generic interfaces have contravariant type parameters; for example: IComparer<T>, IComparable<T>, and IEqualityComparer<T>. These interfaces have only contravariant type parameters, so the type parameters are used only as parameter types in the members of the interfaces.
The following example illustrates contravariant type parameters. The example defines an abstract (MustInherit
in Visual Basic) Shape
class with an Area
property. The example also defines a ShapeAreaComparer
class that implements IComparer<Shape>
(IComparer(Of Shape)
in Visual Basic). The implementation of the IComparer<T>.Compare method is based on the value of the Area
property, so ShapeAreaComparer
can be used to sort Shape
objects by area.
The Circle
class inherits Shape
and overrides Area
. The example creates a SortedSet<T> of Circle
objects, using a constructor that takes an IComparer<Circle>
(IComparer(Of Circle)
in Visual Basic). However, instead of passing an IComparer<Circle>
, the example passes a ShapeAreaComparer
object, which implements IComparer<Shape>
. The example can pass a comparer of a less derived type (Shape
) when the code calls for a comparer of a more derived type (Circle
), because the type parameter of the IComparer<T> generic interface is contravariant.
When a new Circle
object is added to the SortedSet<Circle>
, the IComparer<Shape>.Compare
method (IComparer(Of Shape).Compare
method in Visual Basic) of the ShapeAreaComparer
object is called each time the new element is compared to an existing element. The parameter type of the method (Shape
) is less derived than the type that is being passed (Circle
), so the call is type safe. Contravariance enables ShapeAreaComparer
to sort a collection of any single type, as well as a mixed collection of types, that derive from Shape
.
using System;
using System.Collections.Generic;
abstract class Shape
{
public virtual double Area { get { return 0; }}
}
class Circle : Shape
{
private double r;
public Circle(double radius) { r = radius; }
public double Radius { get { return r; }}
public override double Area { get { return Math.PI * r * r; }}
}
class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>
{
int IComparer<Shape>.Compare(Shape a, Shape b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.Area.CompareTo(b.Area);
}
}
class Program
{
static void Main()
{
// You can pass ShapeAreaComparer, which implements IComparer<Shape>,
// even though the constructor for SortedSet<Circle> expects
// IComparer<Circle>, because type parameter T of IComparer<T> is
// contravariant.
SortedSet<Circle> circlesByArea =
new SortedSet<Circle>(new ShapeAreaComparer())
{ new Circle(7.2), new Circle(100), null, new Circle(.01) };
foreach (Circle c in circlesByArea)
{
Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
}
}
}
/* This code example produces the following output:
null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
*/
Imports System.Collections.Generic
MustInherit Class Shape
Public MustOverride ReadOnly Property Area As Double
End Class
Class Circle
Inherits Shape
Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class
Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)
Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _
Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class
Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}
For Each c As Circle In circlesByArea
Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class
' This code example produces the following output:
'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979
Generic delegates with variant type parameters
The Func
generic delegates, such as Func<T,TResult>, have covariant return types and contravariant parameter types. The Action
generic delegates, such as Action<T1,T2>, have contravariant parameter types. This means that the delegates can be assigned to variables that have more derived parameter types and (in the case of the Func
generic delegates) less derived return types.
Note
The last generic type parameter of the Func
generic delegates specifies the type of the return value in the delegate signature. It is covariant (out
keyword), whereas the other generic type parameters are contravariant (in
keyword).
The following code illustrates this. The first piece of code defines a class named Base
, a class named Derived
that inherits Base
, and another class with a static
method (Shared
in Visual Basic) named MyMethod
. The method takes an instance of Base
and returns an instance of Derived
. (If the argument is an instance of Derived
, MyMethod
returns it; if the argument is an instance of Base
, MyMethod
returns a new instance of Derived
.) In Main()
, the example creates an instance of Func<Base, Derived>
(Func(Of Base, Derived)
in Visual Basic) that represents MyMethod
, and stores it in the variable f1
.
public class Base {}
public class Derived : Base {}
public class Program
{
public static Derived MyMethod(Base b)
{
return b as Derived ?? new Derived();
}
static void Main()
{
Func<Base, Derived> f1 = MyMethod;
Public Class Base
End Class
Public Class Derived
Inherits Base
End Class
Public Class Program
Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function
Shared Sub Main()
Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod
The second piece of code shows that the delegate can be assigned to a variable of type Func<Base, Base>
(Func(Of Base, Base)
in Visual Basic), because the return type is covariant.
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
The third piece of code shows that the delegate can be assigned to a variable of type Func<Derived, Derived>
(Func(Of Derived, Derived)
in Visual Basic), because the parameter type is contravariant.
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
The final piece of code shows that the delegate can be assigned to a variable of type Func<Derived, Base>
(Func(Of Derived, Base)
in Visual Basic), combining the effects of the contravariant parameter type and the covariant return type.
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
Variance in non-generic delegates
In the preceding code, the signature of MyMethod
exactly matches the signature of the constructed generic delegate: Func<Base, Derived>
(Func(Of Base, Derived)
in Visual Basic). The example shows that this generic delegate can be stored in variables or method parameters that have more derived parameter types and less derived return types, as long as all the delegate types are constructed from the generic delegate type Func<T,TResult>.
This is an important point. The effects of covariance and contravariance in the type parameters of generic delegates are similar to the effects of covariance and contravariance in ordinary delegate binding (see Variance in Delegates (C#) and Variance in Delegates (Visual Basic)). However, variance in delegate binding works with all delegate types, not just with generic delegate types that have variant type parameters. Furthermore, variance in delegate binding enables a method to be bound to any delegate that has more restrictive parameter types and a less restrictive return type, whereas the assignment of generic delegates works only if both delegate types are constructed from the same generic type definition.
The following example shows the combined effects of variance in delegate binding and variance in generic type parameters. The example defines a type hierarchy that includes three types, from least derived (Type1
) to most derived (Type3
). Variance in ordinary delegate binding is used to bind a method with a parameter type of Type1
and a return type of Type3
to a generic delegate with a parameter type of Type2
and a return type of Type2
. The resulting generic delegate is then assigned to another variable whose generic delegate type has a parameter of type Type3
and a return type of Type1
, using the covariance and contravariance of generic type parameters. The second assignment requires both the variable type and the delegate type to be constructed from the same generic type definition, in this case, Func<T,TResult>.
using System;
public class Type1 {}
public class Type2 : Type1 {}
public class Type3 : Type2 {}
public class Program
{
public static Type3 MyMethod(Type1 t)
{
return t as Type3 ?? new Type3();
}
static void Main()
{
Func<Type2, Type2> f1 = MyMethod;
// Covariant return type and contravariant parameter type.
Func<Type3, Type1> f2 = f1;
Type1 t1 = f2(new Type3());
}
}
Public Class Type1
End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class
Public Class Program
Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function
Shared Sub Main()
Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod
' Covariant return type and contravariant parameter type.
Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class
Define variant generic interfaces and delegates
Visual Basic and C# have keywords that enable you to mark the generic type parameters of interfaces and delegates as covariant or contravariant.
A covariant type parameter is marked with the out
keyword (Out
keyword in Visual Basic). You can use a covariant type parameter as the return value of a method that belongs to an interface, or as the return type of a delegate. You cannot use a covariant type parameter as a generic type constraint for interface methods.
Note
If a method of an interface has a parameter that is a generic delegate type, a covariant type parameter of the interface type can be used to specify a contravariant type parameter of the delegate type.
A contravariant type parameter is marked with the in
keyword (In
keyword in Visual Basic). You can use a contravariant type parameter as the type of a parameter of a method that belongs to an interface, or as the type of a parameter of a delegate. You can use a contravariant type parameter as a generic type constraint for an interface method.
Only interface types and delegate types can have variant type parameters. An interface or delegate type can have both covariant and contravariant type parameters.
Visual Basic and C# do not allow you to violate the rules for using covariant and contravariant type parameters, or to add covariance and contravariance annotations to the type parameters of types other than interfaces and delegates.
For information and example code, see Variance in Generic Interfaces (C#) and Variance in Generic Interfaces (Visual Basic).
List of types
The following interface and delegate types have covariant and/or contravariant type parameters.
Type | Covariant type parameters | Contravariant type parameters |
---|---|---|
Action<T> to Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Yes | |
Comparison<T> | Yes | |
Converter<TInput,TOutput> | Yes | Yes |
Func<TResult> | Yes | |
Func<T,TResult> to Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Yes | Yes |
IComparable<T> | Yes | |
Predicate<T> | Yes | |
IComparer<T> | Yes | |
IEnumerable<T> | Yes | |
IEnumerator<T> | Yes | |
IEqualityComparer<T> | Yes | |
IGrouping<TKey,TElement> | Yes | |
IOrderedEnumerable<TElement> | Yes | |
IOrderedQueryable<T> | Yes | |
IQueryable<T> | Yes |