<out T> vs <T> en genéricos
¿ Cuál es la diferencia entre <out T>
y <T>
? Por ejemplo:
public interface IExample<out T>
{
...
}
vs.
public interface IExample<T>
{
...
}
La out
palabra clave en genéricos se utiliza para indicar que el tipo T en la interfaz es covariante. Consulte Covarianza y contravarianza para obtener más detalles.
El ejemplo clásico es IEnumerable<out T>
. Como IEnumerable<out T>
es covariante, puedes 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 agregara variación en las interfaces genéricas a C# y VB.NET (en .NET 4 con VS 2010), esto era un error de tiempo de compilación.
Después de .NET 4, IEnumerable<T>
se marcó como covariante y se convirtió en IEnumerable<out T>
. Dado que IEnumerable<out T>
solo usa los elementos que contiene y nunca los agrega o cambia, es seguro tratar 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 Add
método. Supongamos que esto estaría permitido:
IList<string> strings = new List<string>();
IList<object> objects = strings; // NOTE: Fails at compile time
Entonces podrías llamar:
objects.Add(7); // This should work, since IList<object> should let us add **any** object
Por supuesto, esto fallaría, por lo que IList<T>
no se puede marcar como covariante.
Por cierto, también existe una opción para in
que se utiliza en cosas como interfaces de comparación. IComparer<in T>
, por ejemplo, funciona al revés. Puede usar un concreto IComparer<Foo>
directamente como IComparer<Bar>
si Bar
es una subclase de Foo
, porque la IComparer<in T>
interfaz es contravariante .
Para recordar fácilmente el uso de in
una out
palabra clave (también covarianza y contravarianza), podemos imaginar la herencia como una envoltura:
String : Object
Bar : Foo
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<Banana>
porque ICovariantSkinned<T>
es una interfaz covariante y Banana
es un tipo de Fruit
,
la función que acepta ISkinned<Fruit>
sólo podrá aceptar ISkinned<Fruit>
.