Con <out T>
, se puede tratar la referencia de la interfaz como uno hacia arriba en la jerarquía.
Con <in T>
, puede tratar la referencia de interfaz como uno hacia abajo en la jerarquía.
Déjame intentar explicarlo en más términos.
Digamos que está recuperando una lista de animales de su zoo, 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, otros 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é pasa si solo tiene una lista de peces, pero necesita tratarlos como animales, funciona eso? Intuitivamente, que debería funcionar, pero en C# 3.0 y antes, esta pieza de código no se compilará:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
La razón de esto es que el compilador no "sabe" lo que se propone, o puede , hazlo con la colección de animales después de que la hayas recuperado. Por lo que sabe, podría haber una manera de IEnumerable<T>
de poner un objeto de nuevo en la lista, y que potencialmente le permitiría 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"));
lo que el compilador sólo se niega de plano a compilar el código. Esto es covarianza.
Veamos la contravariancia.
Dado que nuestro zoológico puede manejar todos los animales, sin duda puede manejar peces, así que vamos a tratar de agregar algunos peces a nuestro zoológico.
En C# 3.0 y antes, esto no se compila:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));
En este caso, el compilador podría permitir que esta pieza de código, a pesar de que el método devuelve List<Animal>
simplemente porque todos los peces son animales, por lo que si nos acaba de cambiar los tipos de esto:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
Luego de que funcionaría, pero el compilador no puede determinar que usted no está tratando de hacer esto:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
Dado que la lista es en realidad una lista de animales, esto no está permitido.
Por lo tanto, la contravariancia y la covarianza es la forma de tratar las referencias de objeto y lo que se puede 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 entrada -positions, lo que significa argumentos de método y propiedades de solo escritura.
Con out
, se le permite colocar el tipo genérico de salida -positions, que son los valores de retorno del método, de sólo lectura, y propiedades cabo parámetros del método.
Esto le permitirá hacer lo que pensaba hacer con el código:
IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe
List<T>
tiene tanto dentro como fuera de las direcciones en T, por lo que no es ni co-variante ni contra-variante, pero una interfaz que permite vincular un objeto, como esto:
interface IWriteOnlyList<in T>
{
void Add(T value);
}
le permitirá hacer esto:
IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe
He aquí algunos videos que muestra 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)
{
// We can do this since every Descendant is also a Base
// and there is no chance we can put Base objects into
// the returned object, since T is "out"
// We can not, however, put Base objects into b, since all
// Base objects might not be Descendant.
IBibbleOut<Base> b = GetOutDescendant();
// We can do this since every Descendant is also a Base
// and we can now put Descendant objects into Base
// We can not, however, retrieve Descendant objects out
// of d, since all Base objects might not be Descendant
IBibbleIn<Descendant> d = GetInBase();
}
static IBibbleOut<Descendant> GetOutDescendant()
{
return null;
}
static IBibbleIn<Base> GetInBase()
{
return null;
}
}
}
Sin estas marcas, el siguiente podría compilar:
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
Puede ser útil: [Publicación del blog] (http://weblogs.asp.net/paulomorgado/archive/2010/04/13/c-4-0-covariance-and-contravariance-in-generics.aspx) – Krunal
Esta es una expansión corta y buena en mi humilde opinión: http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx – digEmAll
Hmm, está bien pero no lo hace explica por qué, que es lo que realmente me desconcierta. – NibblyPig