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