Notes
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.
Vous pouvez déclarer des paramètres de type générique dans les interfaces en tant que covariant ou contravariant. La covariance permet aux méthodes d’interface d’avoir plus de types de retour dérivés que ceux définis par les paramètres de type générique. La contravariance permet aux méthodes d’interface d’avoir des types d’arguments moins dérivés que ceux spécifiés par les paramètres génériques. Une interface générique qui a des paramètres de type générique covariant ou contravariant est appelée variante.
Remarque
.NET Framework 4 a introduit la prise en charge des variances pour plusieurs interfaces génériques existantes. Pour obtenir la liste des interfaces variant dans .NET, consultez Variance dans les interfaces génériques (C#) .
Déclaration d’interfaces génériques variant
Vous pouvez déclarer des interfaces génériques de variantes à l’aide des mots clés et out
des in
paramètres de type générique.
Important
ref
, in
et les out
paramètres en C# ne peuvent pas être variant. Les types valeur ne prennent pas non plus en charge la variance.
Vous pouvez déclarer un paramètre de type générique covariant à l’aide du out
mot clé. Le type covariant doit remplir les conditions suivantes :
Le type est utilisé uniquement comme type de retour de méthodes d’interface et non utilisé comme type d’arguments de méthode. Ceci est illustré dans l’exemple suivant, dans lequel le type
R
est déclaré covariant.interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }
Il existe une exception à cette règle. Si vous avez un délégué générique contravariant comme paramètre de méthode, vous pouvez utiliser le type comme paramètre de type générique pour le délégué. Ceci est illustré par le type
R
dans l’exemple suivant. Pour plus d’informations, consultez Variance dans les délégués (C#) et Utilisation de variance pour les délégués génériques func et action (C#).interface ICovariant<out R> { void DoSomething(Action<R> callback); }
Le type n’est pas utilisé comme contrainte générique pour les méthodes d’interface. Ceci est illustré dans le code suivant.
interface ICovariant<out R> { // The following statement generates a compiler error // because you can use only contravariant or invariant types // in generic constraints. // void DoSomething<T>() where T : R; }
Vous pouvez déclarer un paramètre de type générique contravariant à l’aide du in
mot clé. Le type contravariant peut être utilisé uniquement comme type d’arguments de méthode et non comme type de retour de méthodes d’interface. Le type contravariant peut également être utilisé pour les contraintes génériques. Le code suivant montre comment déclarer une interface contravariante et utiliser une contrainte générique pour l’une de ses méthodes.
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}
Il est également possible de prendre en charge la covariance et la contravariance dans la même interface, mais pour différents paramètres de type, comme indiqué dans l’exemple de code suivant.
interface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}
Implémentation d’interfaces génériques variant
Vous implémentez des interfaces génériques variant dans des classes à l’aide de la même syntaxe que celle utilisée pour les interfaces invariantes. L’exemple de code suivant montre comment implémenter une interface covariante dans une classe générique.
interface ICovariant<out R>
{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}
Les classes qui implémentent des interfaces variants sont invariantes. Par exemple, prenons le code suivant.
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;
// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;
Extension des interfaces génériques variant
Lorsque vous étendez une interface générique de variante, vous devez utiliser les mots clés et out
les in
mots clés pour spécifier explicitement si l’interface dérivée prend en charge la variance. Le compilateur ne déduit pas la variance de l’interface en cours d’extension. Par exemple, considérez les interfaces suivantes.
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }
Dans l’interface IInvariant<T>
, le paramètre T
de type générique est invariant, tandis que dans IExtCovariant<out T>
le paramètre de type est covariant, bien que les deux interfaces étendent la même interface. La même règle est appliquée aux paramètres de type générique contravariant.
Vous pouvez créer une interface qui étend à la fois l’interface où le paramètre T
de type générique est covariant et l’interface où il est contravariant si dans l’interface d’extension, le paramètre T
de type générique est invariant. Ceci est illustré dans l’exemple de code suivant.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
Toutefois, si un paramètre T
de type générique est déclaré covariant dans une interface, vous ne pouvez pas le déclarer contravariant dans l’interface d’extension, ou inversement. Ceci est illustré dans l’exemple de code suivant.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Éviter l’ambiguïté
Lorsque vous implémentez des interfaces génériques de variantes, la variance peut parfois entraîner une ambiguïté. Cette ambiguïté devrait être évitée.
Par exemple, si vous implémentez explicitement la même interface générique variant avec différents paramètres de type générique dans une classe, elle peut créer une ambiguïté. Le compilateur ne génère pas d’erreur dans ce cas, mais il n’est pas spécifié quelle implémentation d’interface sera choisie au moment de l’exécution. Cette ambiguïté peut entraîner des bogues subtils dans votre code. Considérez l’exemple de code suivant.
// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }
// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
{
Console.WriteLine("Cat");
// Some code.
return null;
}
IEnumerator IEnumerable.GetEnumerator()
{
// Some code.
return null;
}
IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
{
Console.WriteLine("Dog");
// Some code.
return null;
}
}
class Program
{
public static void Test()
{
IEnumerable<Animal> pets = new Pets();
pets.GetEnumerator();
}
}
Dans cet exemple, il n’est pas spécifié comment la pets.GetEnumerator
méthode choisit entre Cat
et Dog
. Cela peut entraîner des problèmes dans votre code.