<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>
{
    ...
}

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 .


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


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>.


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>.


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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.