Comprender las interfaces covariantes y contravariantes en C #

Me he topado con estos en un libro de texto que estoy leyendo en C #, pero estoy teniendo dificultades para entenderlos, probablemente debido a la falta de contexto.

¿Hay una buena explicación concisa de lo que son y para qué son útiles?

Editar para aclarar:

Interfaz covariante:

interface IBibble . . 

Interfaz contravariante:

 interface IBibble . . 

Con , puede tratar la referencia de interfaz como uno hacia arriba en la jerarquía.

Con , puede tratar la referencia de la interfaz como uno hacia abajo en la jerarquía.

Déjame intentar explicarlo en más términos ingleses.

Digamos 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, 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 tienes una lista de peces, pero necesitas tratarlos como animales? ¿Funciona? Intuitivamente, debería funcionar, pero en C # 3.0 y antes, este fragmento de código no se comstackrá:

 IEnumerable animals = GetFishes(); // returns IEnumerable 

La razón de esto es que el comstackdor no “sabe” lo que pretende o puede hacer con la colección de animales después de que la haya recuperado. Por lo que sabe, podría haber un camino a través de IEnumerable para volver a poner un objeto en la lista, y eso potencialmente le permitiría poner un animal que no sea un pez, en una colección que se supone que contiene solo pescado.

En otras palabras, el comstackdor no puede garantizar que esto no esté permitido:

 animals.Add(new Mammal("Zebra")); 

Entonces el comstackdor simplemente se niega a comstackr su 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 intentemos agregar algunos peces a nuestro zoológico.

En C # 3.0 y anteriores, esto no se comstack:

 List fishes = GetAccessToFishes(); // for some reason, returns List fishes.Add(new Fish("Guppy")); 

Aquí, el comstackdor podría permitir esta pieza de código, aunque el método devuelve List simplemente porque todos los peces son animales, así que si cambiamos los tipos a este:

 List fishes = GetAccessToFishes(); fishes.Add(new Fish("Guppy")); 

Entonces funcionaría, pero el comstackdor no puede determinar que no estás tratando de hacer esto:

 List fishes = GetAccessToFishes(); // for some reason, returns List Fish firstFist = fishes[0]; 

Como la lista es en realidad una lista de animales, esto no está permitido.

Entonces, contra y co-varianza es la forma en que trata las referencias de objeto y lo que se le permite hacer con ellas.

Las palabras clave de out y out en C # 4.0 marcan específicamente la interfaz como una u otra. Con in , puede colocar el tipo genérico (normalmente T) en las entradas de entrada , lo que significa argumentos de método y propiedades de solo escritura.

Sin out , puede colocar el tipo genérico en las posiciones de salida , que son los valores de retorno del método, las propiedades de solo lectura y los parámetros del método de salida.

Esto le permitirá hacer lo que se pretende hacer con el código:

 IEnumerable animals = GetFishes(); // returns IEnumerable // since we can only get animals *out* of the collection, every fish is an animal // so this is safe 

List tiene direcciones de entrada y de salida en T, por lo que no es covariante ni contravariante, sino una interfaz que le permitió agregar objetos, como este:

 interface IWriteOnlyList { void Add(T value); } 

te permitiría hacer esto:

 IWriteOnlyList fishes = GetWriteAccessToAnimals(); // still returns IWriteOnlyList fishes.Add(new Fish("Guppy")); <-- this is now safe 

Aquí hay algunos videos que muestran los conceptos:

  • Covarianza y contradicción - VS2010 C # Parte 1 de 3
  • Covarianza y contradicción - VS2010 C # Parte 2 de 3
  • Covarianza y contradicción - VS2010 C # Parte 3 de 3

Aquí hay un ejemplo:

 namespace SO2719954 { class Base { } class Descendant : Base { } interface IBibbleOut { } interface IBibbleIn { } 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 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 d = GetInBase(); } static IBibbleOut GetOutDescendant() { return null; } static IBibbleIn GetInBase() { return null; } } } 

Sin estas marcas, lo siguiente podría comstackrse:

 public List GetDescendants() ... List bases = GetDescendants(); bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant 

o esto:

 public List GetBases() ... List descendants = GetBases(); <-- uh-oh, we try to treat all Bases as Descendants 

Esta publicación es la mejor que he leído sobre el tema

En resumen, la covarianza / contravariancia / invariancia se refiere a la conversión automática de tipos (de base a derivada y viceversa). Esos lanzamientos de tipos son posibles solo si se respetan algunas garantías en términos de acciones de lectura / escritura realizadas en los objetos lanzados. Lee la publicación para más detalles.