Con <out T>
, puede tratar la referencia de interfaz como una hacia arriba en la jerarquía.
Con <in T>
, puede tratar la referencia de la interfaz como una hacia abajo en la jerarquía.
Voy a intentar explicarlo en términos más ingleses.
Supongamos que está recuperando una lista de animales de su zoológico y tiene la intención de procesarlos. Todos los animales (en su zoológico) tienen un nombre y una identificación única. Algunos animales son mamíferos, algunos son reptiles, algunos son anfibios, algunos son peces, etc. pero todos son animales.
Entonces, con su lista de animales (que contiene animales de diferentes tipos), puede decir que todos los animales tienen un nombre, por lo que obviamente sería seguro obtener el nombre de todos los animales.
Sin embargo, ¿qué sucede si solo tiene una lista de peces, pero necesita tratarlos como animales? ¿Funciona? Intuitivamente, debería funcionar, pero en C # 3.0 y antes, este fragmento de código no se compilará:
IEnumerable<Animal> animals = GetFishes();
La razón de esto es que el compilador no "sabe" lo que pretende, o puede , hacer con la colección de animales después de haberla recuperado. Por lo que sabe, podría haber una forma IEnumerable<T>
de volver a poner un objeto en la lista, y eso podría permitirle poner un animal que no sea un pez, en una colección que se supone que contiene solo peces.
En otras palabras, el compilador no puede garantizar que esto no esté permitido:
animals.Add(new Mammal("Zebra"));
Así que el compilador simplemente se niega a compilar su código. Esta es la covarianza.
Veamos la contravarianza.
Dado que nuestro zoológico puede manejar todos los animales, ciertamente puede manejar peces, así que intentemos agregar algunos peces a nuestro zoológico.
En C # 3.0 y antes, esto no se compila:
List<Fish> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Aquí, el compilador podría permitir este fragmento de código, aunque el método devuelve List<Animal>
simplemente porque todos los peces son animales, así que si cambiamos los tipos a esto:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Entonces funcionaría, pero el compilador no puede determinar que no estás intentando hacer esto:
List<Fish> fishes = GetAccessToFishes();
Fish firstFist = fishes[0];
Dado que la lista es en realidad una lista de animales, esto no está permitido.
Entonces, la contravarianza y la covarianza es cómo se tratan las referencias de objetos y qué se le permite hacer con ellas.
Las palabras clave in
y out
en C # 4.0 marcan específicamente la interfaz como una u otra. Con in
, puede colocar el tipo genérico (generalmente T) en posiciones de entrada , lo que significa argumentos de método y propiedades de solo escritura.
Con out
, se le permite colocar el tipo genérico en posiciones de salida , que son valores de retorno de método, propiedades de solo lectura y parámetros de método de salida.
Esto le permitirá hacer lo que pretendía hacer con el código:
IEnumerable<Animal> animals = GetFishes();
List<T>
tiene direcciones de entrada y salida en T, por lo que no es ni covariante ni contravariante, sino una interfaz que le permite agregar objetos, como este:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
te permitiría hacer esto:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals();
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
Aquí hay algunos videos que muestran los conceptos:
He aquí un ejemplo:
namespace SO2719954
{
class Base { }
class Descendant : Base { }
interface IBibbleOut<out T> { }
interface IBibbleIn<in T> { }
class Program
{
static void Main(string[] args)
{
IBibbleOut<Base> b = GetOutDescendant();
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Sin estas marcas, se podría compilar lo siguiente:
public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant
o esto:
public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
as Descendants