Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
La covariance et la contravariance décrivent le fonctionnement des conversions de référence entre les types génériques construits lorsque leurs arguments de type sont liés par héritage, par exemple, entre IEnumerable<Derived> et IEnumerable<Base>. La variance est une propriété d’une interface générique ou du paramètre de type délégué, et contrôle les conversions implicites entre les types construits qui utilisent différents arguments de type. Par défaut, les paramètres de type générique sont invariants, même si un argument de type dérive d’un autre, les types génériques construits correspondants, tels que List<Derived> et List<Base>, ne sont pas liés, sauf si le paramètre de type est explicitement déclaré comme covariant ou contravariant.
Les définitions et exemples suivants supposent qu’une classe de base nommée Base et une classe dérivée nommée Derived.
Les arguments de type invariant nécessitent que vous utilisiez exactement le type spécifié. Vous ne pouvez pas remplacer un type dérivé ou de base.
Par exemple, vous ne pouvez pas affecter une instance d’une
List<Base>variable de typeList<Derived>, ou inversement.Les paramètres de type covariant vous permettent de remplacer un type plus dérivé pour l’argument de type d’origine.
Par exemple, vous pouvez affecter une instance d’une
IEnumerable<Derived>variable de typeIEnumerable<Base>.Les paramètres de type contravariant vous permettent de remplacer un type de base pour un argument de type dérivé au lieu de l’original.
Par exemple, vous pouvez affecter une instance d’une
Action<Base>variable de typeAction<Derived>.
Les paramètres de type covariant vous permettent d’effectuer des affectations qui ressemblent beaucoup à polymorphisme ordinaire, comme illustré dans le code suivant.
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
La List<T> classe implémente l’interface IEnumerable<T> , donc List<Derived> (List(Of Derived) en Visual Basic) implémente IEnumerable<Derived>. Le paramètre de type covariant effectue le reste.
La contravariance, en revanche, paraît peu intuitive. L’exemple suivant crée un délégué de type Action<Base> (Action(Of Base) en Visual Basic), puis affecte ce délégué à une variable de 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())
Cela peut paraître rétrograde, mais c'est le code de type sécurisé qui est compilé et exécuté. L’expression lambda correspond au délégué auquel il est affecté. Elle définit donc une méthode qui accepte un paramètre de type Base et qui n’a aucune valeur de retour. Le délégué résultant peut être affecté à une variable de type Action<Derived> , car le paramètre T de type du Action<T> délégué est contravariant. Le code est de type sécurisé, car T spécifie un type de paramètre. Lorsque le délégué de type Action<Base> est appelé comme s’il s’agissait d’un délégué de type Action<Derived>, son argument doit être de type Derived. Cet argument peut toujours être transmis en toute sécurité à la méthode sous-jacente, car le paramètre de la méthode est de type Base.
En général, un paramètre de type covariant peut être utilisé comme type de retour d’un délégué, et les paramètres de type contravariant peuvent être utilisés comme types de paramètres. Pour une interface, les paramètres de type covariant peuvent être utilisés comme types de retour des méthodes de l’interface, et les paramètres de type contravariant peuvent être utilisés comme types de paramètres des méthodes de l’interface.
La covariance et la contravariance sont collectivement appelées variance. Un paramètre de type générique qui n’est pas marqué covariant ou contravariant est appelé invariant. Voici un bref résumé des faits concernant la variance dans le Common Language Runtime.
Les paramètres de type variant sont limités à l’interface générique et aux types délégués génériques.
Une interface générique ou un type délégué générique peut avoir à la fois des paramètres de type covariant et contravariant.
La variance s’applique uniquement aux types de référence ; si vous spécifiez un type valeur pour un paramètre de type variant, ce paramètre de type est invariant pour le type construit résultant.
La variance ne s'applique pas à la combinaison de délégués. Autrement dit, étant donné deux délégués de types
Action<Derived>et (Action<Base>etAction(Of Derived)Action(Of Base)en Visual Basic), vous ne pouvez pas combiner le deuxième délégué avec le premier, bien que le résultat soit de type sécurisé. La variance permet au deuxième délégué d’être affecté à une variable de typeAction<Derived>, mais les délégués peuvent combiner uniquement si leurs types correspondent exactement.À compter de C# 9, les types de retour covariants sont pris en charge. Une méthode de substitution peut déclarer un type de retour plus dérivé que celui de la méthode qu'elle substitue, et une propriété de substitution en lecture seule peut également déclarer un type plus dérivé.
Interfaces génériques avec des paramètres de type covariant
Plusieurs interfaces génériques ont des paramètres de type covariants, par exemple, IEnumerable<T>, IEnumerator<T>, IQueryable<T>et IGrouping<TKey,TElement>. Tous les paramètres de type de ces interfaces sont covariants. Les paramètres de type sont donc utilisés uniquement pour les types de retour des membres.
L’exemple suivant illustre les paramètres de type covariant. L’exemple définit deux types : Base a une méthode statique nommée PrintBases qui prend un IEnumerable<Base> (IEnumerable(Of Base) en Visual Basic) et imprime les éléments.
Derived hérite de Base. L’exemple crée un objet vide List<Derived> (List(Of Derived) en Visual Basic) et montre que ce type peut être passé à PrintBases et assigné à une variable de type IEnumerable<Base> sans conversion.
List<T> implémente IEnumerable<T>, qui a un seul paramètre de type covariant. Le paramètre de type covariant est la raison pour laquelle une instance de IEnumerable<Derived> peut être utilisée au lieu de 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
Interfaces génériques avec des paramètres de type contravariant
Plusieurs interfaces génériques ont des paramètres de type contravariant ; par exemple : IComparer<T>, IComparable<T>et IEqualityComparer<T>. Ces interfaces n’ont que des paramètres de type contravariant, de sorte que les paramètres de type sont utilisés uniquement comme types de paramètres dans les membres des interfaces.
L’exemple suivant illustre les paramètres de type contravariant. L’exemple définit une classe abstraite (MustInherit en Visual Basic) Shape avec une Area propriété. L’exemple définit également une ShapeAreaComparer classe qui implémente IComparer<Shape> (IComparer(Of Shape) en Visual Basic). L’implémentation de la IComparer<T>.Compare méthode est basée sur la valeur de la Area propriété. Elle peut donc ShapeAreaComparer être utilisée pour trier Shape les objets par zone.
La Circle classe hérite Shape et remplace Area. L'exemple crée un SortedSet<T> d'objets Circle , à l'aide d'un constructeur qui accepte un IComparer<Circle> (IComparer(Of Circle) dans Visual Basic). Toutefois, au lieu de passer un IComparer<Circle>, l’exemple transmet un ShapeAreaComparer objet, qui implémente IComparer<Shape>. L’exemple peut passer un comparateur d’un type moins dérivé (Shape) lorsque le code appelle un comparateur d’un type plus dérivé (Circle), car le paramètre de type de l’interface IComparer<T> générique est contravariant.
Lorsqu’un nouvel Circle objet est ajouté à la SortedSet<Circle>, la méthode IComparer<Shape>.Compare (méthode IComparer(Of Shape).Compare en Visual Basic) de l’objet ShapeAreaComparer est appelée chaque fois que le nouvel élément est comparé à un élément existant. Le type de paramètre de la méthode (Shape) est moins dérivé que le type passé (Circle), par conséquent l'appel est sécurisé. La contravariance permet à ShapeAreaComparer de trier une collection de n'importe quel type particulier, ainsi qu'une collection de types mélangés, qui sont dérivés de 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
Délégués génériques avec des paramètres de type variant
Les Func délégués génériques, tels que Func<T,TResult>, ont des types de retour covariants et des types de paramètres contravariants. Les Action délégués génériques, tels que Action<T1,T2>, ont des types de paramètres contravariants. Cela signifie que les délégués peuvent être affectés à des variables qui ont des types de paramètres plus dérivés et (dans le cas des Func délégués génériques) moins de types de retour dérivés.
Remarque
Le dernier paramètre de type générique des Func délégués génériques spécifie le type de la valeur de retour dans la signature du délégué. Il s’agit de covariant (out mot clé), tandis que les autres paramètres de type générique sont contravariants (in mot clé).
Le code suivant illustre cela. Le premier élément de code définit une classe nommée Base, une classe nommée Derived qui hérite Base, et une autre classe avec une static méthode (Shared en Visual Basic) nommée MyMethod. La méthode prend une instance de Base et retourne une instance de Derived. (Si l’argument est une instance de Derived, MyMethod le renvoie ; si l’argument est une instance de Base, MyMethod retourne une nouvelle instance de Derived.) Dans Main(), l’exemple crée une instance de Func<Base, Derived> (Func(Of Base, Derived) en Visual Basic) qui représente MyMethod, et la stocke dans la 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
Le deuxième élément de code montre que le délégué peut être affecté à une variable de type Func<Base, Base> (Func(Of Base, Base) en Visual Basic), car le type de retour est 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())
Le troisième élément de code montre que le délégué peut être affecté à une variable de type Func<Derived, Derived> (Func(Of Derived, Derived) en Visual Basic), car le type de paramètre est 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())
Le dernier élément de code montre que le délégué peut être affecté à une variable de type Func<Derived, Base> (Func(Of Derived, Base) en Visual Basic), en combinant les effets du type de paramètre contravariant et du type de retour covariant.
// 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 dans les délégués non génériques
Dans le code précédent, la signature de MyMethod exactement correspond à la signature du délégué générique construit : Func<Base, Derived> (Func(Of Base, Derived) en Visual Basic). L’exemple montre que ce délégué générique peut être stocké dans des variables ou des paramètres de méthode qui ont des types de paramètres plus dérivés et des types de retour moins dérivés, tant que tous les types délégués sont construits à partir du type délégué générique Func<T,TResult>.
C’est un point important. Les effets de la covariance et de la contravariance dans les paramètres de type des délégués génériques sont similaires aux effets de la covariance et de la contravariance dans la liaison de délégués ordinaires (voir Variance dans les délégués (C#) et Variance dans les délégués (Visual Basic)). Toutefois, la variance dans la liaison de délégués fonctionne avec tous les types délégués, et pas seulement les types délégués génériques qui ont des paramètres de type variant. En outre, la variance dans la liaison de délégué permet à une méthode d’être liée à un délégué qui a des types de paramètres plus restrictifs et un type de retour moins restrictif, tandis que l’attribution de délégués génériques fonctionne uniquement si les deux types délégués sont construits à partir de la même définition de type générique.
L’exemple suivant montre les effets combinés de la variante dans l'association des délégués et dans la variante des paramètres de type générique. L’exemple définit une hiérarchie de types qui comprend trois types, du moins dérivé (Type1) au plus dérivé (Type3). La variance dans la liaison de délégué ordinaire est utilisée pour lier une méthode dotée d'un type de paramètre Type1 et d'un type de retour Type3 à un délégué générique ayant un type de paramètre Type2 et un type de retour Type2. Le délégué générique résultant est ensuite affecté à une autre variable dont le type de délégué générique a un paramètre de type Type3 et un type de retour de Type1, à l’aide de la covariance et de la contravariance des paramètres de type générique. La deuxième affectation nécessite à la fois que le type de variable et le type de délégué soient construits à partir de la même définition de type générique, dans ce cas 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
Définir des interfaces génériques et des délégués variants
Visual Basic et C# ont des mots clés qui vous permettent de marquer les paramètres de type générique des interfaces et des délégués comme covariants ou contravariants.
Un paramètre de type covariant est marqué avec le out mot clé (Out mot clé en Visual Basic). Vous pouvez utiliser un paramètre de type covariant comme valeur de retour d’une méthode qui appartient à une interface ou comme type de retour d’un délégué. Vous ne pouvez pas utiliser un paramètre de type covariant comme contrainte de type générique pour les méthodes d’interface.
Remarque
Si une méthode d’interface a un paramètre qui est un type délégué générique, un paramètre de type covariant du type d’interface peut être utilisé pour spécifier un paramètre de type contravariant du type délégué.
Un paramètre de type contravariant est marqué avec le in mot clé (In mot clé en Visual Basic). Vous pouvez utiliser un paramètre de type contravariant comme type d’un paramètre d’une méthode qui appartient à une interface ou comme type d’un paramètre d’un délégué. Vous pouvez utiliser un paramètre de type contravariant comme contrainte de type générique pour une méthode d’interface.
Seuls les types d’interface et les types délégués peuvent avoir des paramètres de type variant. Une interface ou un type délégué peut avoir à la fois des paramètres de type covariant et contravariant.
Visual Basic et C# ne vous permettent pas de violer les règles d’utilisation des paramètres de type covariant et contravariant, ou d’ajouter des annotations de covariance et de contravariance aux paramètres de type des types autres que les interfaces et les délégués.
Pour plus d’informations et pour obtenir des exemples de code, consultez Variance dans les interfaces génériques (C#) et Variance dans les interfaces génériques (Visual Basic).
Liste des types
Les types d’interface et de délégué suivants ont des paramètres de type covariant et/ou contravariant.
| Catégorie | Paramètres de type covariant | Paramètres de type contravariant |
|---|---|---|
| Action<T> à Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Oui | |
| Comparison<T> | Oui | |
| Converter<TInput,TOutput> | Oui | Oui |
| Func<TResult> | Oui | |
| Func<T,TResult> à Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Oui | Oui |
| IComparable<T> | Oui | |
| Predicate<T> | Oui | |
| IComparer<T> | Oui | |
| IEnumerable<T> | Oui | |
| IEnumerator<T> | Oui | |
| IEqualityComparer<T> | Oui | |
| IGrouping<TKey,TElement> | Oui | |
| IOrderedEnumerable<TElement> | Oui | |
| IOrderedQueryable<T> | Oui | |
| IQueryable<T> | Oui |