<out T> vs <T> en genéricos

189

¿Cuál es la diferencia entre <out T>y <T>? Por ejemplo:

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

vs.

public interface IExample<T>
{
    ...
}
Cole Johnson
fuente
1
Un buen ejemplo sería IObservable <T> e IObserver <T>, definidos en el sistema ns en mscorlib. interfaz pública IObservable <out T> e interfaz pública IObserver <in T>. Del mismo modo, IEnumerator <out T>, IEnumerable <out T>
VivekDev
2
La mejor explicación que conocí: agirlamonggeeks.com/2019/05/29/… . (<En T> <- significa que T solo se puede pasar como parámetro a un método; <out T> <- significa que T puede ser solo regresó como resultados del método)
Uladzimir Sharyi

Respuestas:

212

La outpalabra clave en genéricos se usa para denotar que el tipo T en la interfaz es covariante. Ver Covarianza y contravarianza para más detalles.

El ejemplo clásico es IEnumerable<out T>. Como IEnumerable<out T>es covariante, puede hacer lo siguiente:

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

La segunda línea anterior fallaría si esto no fuera covariante, aunque lógicamente debería funcionar, ya que la cadena deriva del objeto. Antes de que se agregaran variaciones en las interfaces genéricas a C # y VB.NET (en .NET 4 con VS 2010), esto fue un error de tiempo de compilación.

Después de .NET 4, IEnumerable<T>se marcó covariante y se convirtió IEnumerable<out T>. Dado que IEnumerable<out T>solo usa los elementos que contiene y nunca los agrega / cambia, es seguro que trate una colección enumerable de cadenas como una colección enumerable de objetos, lo que significa que es covariante .

Esto no funcionaría con un tipo como IList<T>, ya que IList<T>tiene un Addmétodo. Supongamos que esto se permitiría:

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

Entonces puedes llamar:

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

Esto, por supuesto, fallaría, por IList<T>lo que no se puede marcar como covariante.

También hay, por cierto, una opción para in, que es utilizada por cosas como interfaces de comparación. IComparer<in T>, por ejemplo, funciona de manera opuesta. Puede usar un concreto IComparer<Foo>directamente como IComparer<Bar>si Bares una subclase de Foo, porque la IComparer<in T>interfaz es contravariante .

Reed Copsey
fuente
44
@ColeJohnson Porque Imagees una clase abstracta;) Puedes hacerlo new List<object>() { Image.FromFile("test.jpg") };sin problemas, o también puedes hacerlo new List<object>() { new Bitmap("test.jpg") };. El problema con el tuyo es que new Image()no está permitido (tampoco puedes hacerlo var img = new Image();)
Reed Copsey
44
un genérico IList<object>es un ejemplo extraño, si quieres objectno necesitas genéricos.
Jodrell
55
@ReedCopsey ¿No estás contradiciendo tu propia respuesta en tu comentario?
MarioDS
64

Para recordar fácilmente el uso de iny la outpalabra clave (también covarianza y contravarianza), podemos representar la herencia como envoltura:

String : Object
Bar : Foo

En fuera

o0omycomputero0o
fuente
11
¿No es este el camino equivocado? Contravarianza = in = permite que se usen tipos menos derivados en lugar de más derivados. / Covarianza = out = permite que se usen más tipos derivados en lugar de menos derivados. Personalmente, mirando su diagrama, lo leo como lo opuesto a eso.
Sam Shiles
co u variant (: para mí
snr
49

considerar,

class Fruit {}

class Banana : Fruit {}

interface ICovariantSkinned<out T> {}

interface ISkinned<T> {}

y las funciones,

void Peel(ISkinned<Fruit> skinned) { }

void Peel(ICovariantSkinned<Fruit> skinned) { }

La función que acepta ICovariantSkinned<Fruit>podrá aceptar ICovariantSkinned<Fruit>o ICovariantSkinned<Bananna>porque ICovariantSkinned<T>es una interfaz covariante y Bananaes un tipo de Fruit,

la función que acepta ISkinned<Fruit>solo podrá aceptar ISkinned<Fruit>.

Jodrell
fuente
38

" out T" significa que el tipo Tes "covariante". Eso limita Ta aparecer solo como un valor devuelto (saliente) en los métodos de la clase genérica, interfaz o método. La implicación es que puede convertir el tipo / interfaz / método a un equivalente con un supertipo de T.
Por ejemplo, ICovariant<out Dog>se puede echar a ICovariant<Animal>.

James World
fuente
14
No me di cuenta de que las outejecuciones solo Tse pueden devolver, hasta que leí esta respuesta. ¡Todo el concepto tiene más sentido ahora!
MarioDS
6

Desde el enlace que publicaste ...

Para los parámetros de tipo genérico, la palabra clave out especifica que el parámetro de tipo es covariante .

EDITAR : nuevamente, desde el enlace que publicaste

Para obtener más información, vea Covarianza y contravarianza (C # y Visual Basic). http://msdn.microsoft.com/en-us/library/ee207183.aspx

Brad Cunningham
fuente