2010-04-27 9 views
69

He encontrado esto en un libro de texto que estoy leyendo en C#, pero tengo dificultades para entenderlos, probablemente debido a la falta de contexto.Comprender las interfaces covariantes y contravariantes en C#

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

Editar una aclaración:

interfaz covariante:

interface IBibble<out T> 
. 
. 

interfaz contravariante:

interface IBibble<in T> 
. 
. 
+0

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

+2

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

+0

Hmm, está bien pero no lo hace explica por qué, que es lo que realmente me desconcierta. – NibblyPig

Respuesta

113

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 
+0

Hmm, ¿podría explicar el objetivo de la covarianza y la contravarianza? Puede ayudarme a entenderlo más. – NibblyPig

+1

Vea el último bit, que es lo que el compilador evitó antes, el propósito de entrada y salida es decir qué puede hacer con las interfaces (o tipos) que es seguro, para que el compilador no le impida hacer las operaciones cosas. –

+0

Excelente respuesta, vi los videos que fueron muy útiles, y combinados con su ejemplo lo tengo ordenado ahora. Solo queda una pregunta, y esa es la razón por la cual se requieren 'fuera' y 'en', ¿por qué el estudio visual no sabe automáticamente qué estás tratando de hacer (o cuál es la razón detrás de esto)? – NibblyPig

4

This post es el mejor que he leído sobre el tema

En resumen, la covarianza/contravarianza/invariancia se ocupa de 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.

+0

muy buen artículo de hecho. :) gracias – gsimoes

+4

El enlace aparece muerto. Aquí hay una versión archivada: https://web.archive.org/web/20140626123445/http://adamnathan.co.uk/?p=75 – si618

Cuestiones relacionadas