<out T> vs <T> dans les génériques

189

Quelle est la différence entre <out T>et <T>? Par exemple:

public interface IExample<out T>
{
    ...
}

contre.

public interface IExample<T>
{
    ...
}
Cole Johnson
la source
1
Un bon exemple serait IObservable <T> et IObserver <T>, définis dans system ns dans mscorlib. interface publique IObservable <out T> et interface publique IObserver <in T>. De même, IEnumerator <out T>, IEnumerable <out T>
VivekDev
2
La meilleure explication que j'ai rencontrée: agirlamonggeeks.com/2019/05/29/… . (< In T> <- signifie que T ne peut être passé que comme paramètre à une méthode; <out T> <- signifie que T peut être seulement retourné comme résultats de méthode)
Uladzimir Sharyi

Réponses:

212

Le outmot-clé en générique est utilisé pour indiquer que le type T dans l'interface est covariant. Voir Covariance et contravariance pour plus de détails.

L'exemple classique est IEnumerable<out T>. Puisque IEnumerable<out T>est covariant, vous êtes autorisé à effectuer les opérations suivantes:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

La deuxième ligne ci-dessus échouerait si ce n'était pas covariant, même si logiquement cela devrait fonctionner, puisque string dérive de object. Avant l' ajout de la variance dans les interfaces génériques à C # et VB.NET (dans .NET 4 avec VS 2010), il s'agissait d'une erreur de compilation.

Après .NET 4, a IEnumerable<T>été marqué comme covariant et est devenu IEnumerable<out T>. Comme IEnumerable<out T>il n'utilise que les éléments qu'il contient, et ne les ajoute / ne change jamais, il est prudent de traiter une collection énumérable de chaînes comme une collection énumérable d'objets, ce qui signifie qu'elle est covariante .

Cela ne fonctionnerait pas avec un type comme IList<T>, car IList<T>a une Addméthode. Supposons que cela soit autorisé:

IList<string> strings = new List<string>();
IList<object> objects = strings;  // NOTE: Fails at compile time

Vous pourriez alors appeler:

objects.Add(new Image()); // This should work, since IList<object> should let us add **any** object

Cela échouerait, bien sûr, et ne IList<T>peut donc pas être marqué comme covariant.

Il y a aussi, btw, une option pour in- qui est utilisée par des choses comme les interfaces de comparaison. IComparer<in T>, par exemple, fonctionne dans le sens inverse. Vous pouvez utiliser un béton IComparer<Foo>directement comme IComparer<Bar>si Barest une sous-classe de Foo, car l' IComparer<in T>interface est contravariante .

Reed Copsey
la source
4
@ColeJohnson Because Imageest une classe abstraite;) Vous pouvez le faire new List<object>() { Image.FromFile("test.jpg") };sans problème, ou vous pouvez le faire new List<object>() { new Bitmap("test.jpg") };aussi. Le problème avec le vôtre est que ce new Image()n'est pas autorisé (vous ne pouvez pas le faire non var img = new Image();plus)
Reed Copsey
4
un générique IList<object>est un exemple bizarre, si vous voulez des objectgénériques, vous n'avez pas besoin de génériques.
Jodrell
5
@ReedCopsey Ne contredisez-vous pas votre propre réponse dans votre commentaire?
MarioDS
64

Pour se souvenir facilement de l'utilisation de inet du outmot - clé (également covariance et contravariance), nous pouvons représenter l'héritage comme un wrapping:

String : Object
Bar : Foo

entrée / sortie

o0omycomputero0o
la source
11
N'est-ce pas la mauvaise façon de faire? Contravariance = in = permet d'utiliser des types moins dérivés à la place de plus dérivés. / Covariance = out = permet d'utiliser plus de types dérivés à la place de moins dérivés. Personnellement, en regardant votre diagramme, je l'ai lu comme le contraire de cela.
Sam Shiles
co u variante (: for me
snr
49

considérer,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

et les fonctions,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

La fonction qui accepte ICovariantSkinned<Fruit>pourra accepter ICovariantSkinned<Fruit>ou ICovariantSkinned<Bananna>car ICovariantSkinned<T>est une interface covariante et Bananaest un type de Fruit,

la fonction qui accepte ISkinned<Fruit>ne pourra accepter ISkinned<Fruit>.

Jodrell
la source
38

" out T" signifie que ce type Test "covariant". Cela limite Tà apparaître uniquement comme une valeur retournée (sortante) dans les méthodes de la classe, de l'interface ou de la méthode générique. L'implication est que vous pouvez convertir le type / interface / méthode en un équivalent avec un super-type de T.
Par exemple, ICovariant<out Dog>peut être jeté vers ICovariant<Animal>.

James World
la source
14
Je n'avais pas réalisé que les outdemandes de règlement ne Tpeuvent être retournées que lorsque j'ai lu cette réponse. L'ensemble du concept a plus de sens maintenant!
MarioDS
6

À partir du lien que vous avez publié ...

Pour les paramètres de type générique, le mot clé out spécifie que le paramètre de type est covariant .

EDIT : Encore une fois, à partir du lien que vous avez publié

Pour plus d'informations, consultez Covariance et Contravariance (C # et Visual Basic). http://msdn.microsoft.com/en-us/library/ee207183.aspx

Brad Cunningham
la source