generic c# generics covariance

generic - c# in keyword



Covarianza con C#Genéricos (7)

Dada una interfaz IQuestion y una implementación de esa interfaz AMQuestion , supongamos el siguiente ejemplo:

List<AMQuestion> typed = new List<AMQuestion>(); IList<IQuestion> nonTyped = typed;

Este ejemplo produce, como se esperaba, un error de compilación que dice que los dos no son del mismo tipo. Pero establece que existe una conversión explícita. Así que lo cambio para que se vea así:

List<AMQuestion> typed = new List<AMQuestion>(); IList<IQuestion> nonTyped = typed as IList<IQuestion>;

Que luego se compila pero, en tiempo de ejecución, no nonTyped siempre es nulo. Si alguien pudiera explicar dos cosas:

  • Por qué esto no funciona.
  • Cómo puedo conseguir el efecto deseado.

Podria ser muy apreciado. ¡Gracias!


Debido a que List<T> no es una clase sellada, sería posible que exista un tipo que heredaría de List<AMQuestion> e implementaría IList<IQuestion> . A menos que implementes un tipo de este tipo, es extremadamente improbable que alguna vez exista. No obstante, sería perfectamente legítimo decir, por ejemplo,

class SillyList : List<AMQuestion>, IList<IQuestion> { ... }

e implemente explícitamente todos los miembros específicos de tipo de IList<IQuestion> . Por lo tanto, también sería perfectamente legítimo decir "Si esta variable contiene una referencia a una instancia de un tipo derivado de la List<AMQuestion> , y si el tipo de esa instancia también implementa IList<IQuestion> , convierta la referencia al último tipo.


El hecho de que AMQuestion implemente la interfaz IQuestion no se traduce en la List<AMQuestion> derivada de la List<IQuestion> .

Debido a que este lanzamiento es ilegal, su operador devuelve null .

Debes lanzar cada artículo individualmente como tal:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();

Con respecto a su comentario, considere el siguiente código, con los ejemplos habituales de animales de cliché:

//Lizard and Donkey inherit from Animal List<Lizard> lizards = new List<Lizard> { new Lizard() }; List<Donkey> donkeys = new List<Donkey> { new Donkey() }; List<Animal> animals = lizards as List<Animal>; //let''s pretend this doesn''t return null animals.Add(new Donkey()); //Reality unravels!

si se nos permitiera lanzar la List<Lizard> a una List<Animal> , entonces podríamos, en teoría, agregar un nuevo Donkey a esa lista, lo que rompería la herencia.


El operador "as" siempre devolverá null allí ya que no existe una conversión válida, esto es un comportamiento definido. Tienes que convertir o lanzar la lista de esta manera:

IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();


El problema es que la List<AMQuestion> no se puede convertir a IList<IQuestion> , por lo que usar el operador as no ayuda. La conversión explícita en este caso significa convertir AMQuestion a IQuestion :

IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();

Por cierto, tiene el término "Covarianza" en su título. En IList el tipo no es covariante. Esto es exactamente por qué el elenco no existe. La razón es que la interfaz IList tiene T en algunos parámetros y en algunos valores de retorno, por lo que ni T in ni out pueden usarse. (@Sneftel tiene un buen ejemplo para mostrar por qué este lanzamiento no está permitido).

Si solo necesita leer de la lista, puede usar IEnumerable en IEnumerable lugar:

IEnumerable<IQuestion> = typed;

Esto funcionará porque IEnumerable<out T> ha definido, ya que no se puede pasar una T como parámetro. Por lo general, debería hacer posible la "promesa" más débil en su código para mantenerlo extensible.


Por qué no funciona: as devuelve null si el tipo dinámico del valor no se puede convertir al tipo de destino, y la List<AMQuestion> no se puede convertir a IList<IQuestion> .

Pero ¿por qué no puede? Bueno, compruébalo:

List<AMQuestion> typed = new List<AMQuestion>(); IList<IQuestion> nonTyped = typed as IList<IQuestion>; nonTyped.Add(new OTQuestion()); AMQuestion whaaaat = typed[0];

IList<IQuestion> dice "Puedes IQuestion cualquier IQuestion ". Pero esa es una promesa que no podría cumplir si se tratara de una List<AMQuestion> .

Ahora, si no desea agregar nada, solo IQuestion como una colección de cosas compatibles con IQuestion , entonces lo mejor sería IReadOnlyList<IQuestion> a una IReadOnlyList<IQuestion> de List.AsReadOnly con List.AsReadOnly . Dado que una lista de solo lectura no puede tener cosas extrañas agregadas, puede ser emitida correctamente.


Un tipo con un parámetro de tipo genérico solo puede ser covariante si este tipo genérico ocurre solo en accesos de lectura y contravariante, si ocurre solo en accesos de escritura. IList<T> permite el acceso de lectura y escritura a valores de tipo T , por lo que no puede ser una variante.

Supongamos que se le permitió asignar una List<AMQuestion> a una variable de tipo IList<IQuestion> . Ahora implementemos una class XYQuestion : IQuestion e insertemos un valor de ese tipo en nuestra IList<IQuestion> , que parece perfectamente legal. Esta lista aún hace referencia a una List<AMQuestion> , pero no podemos insertar una XYQuestion en una List<AMQuestion> ! Por lo tanto, los dos tipos de listas no son compatibles con la asignación.

IList<IQuestion> list = new List<AMQuestion>(); // Not allowed! list.Add(new XYQuestion()); // Uuups!


IList<T> no es covariante para T ; no puede ser, ya que la interfaz define funciones que toman valores de tipo T en una posición de "entrada". Sin embargo, IEnumerable<T> es covariante para T Si puede limitar su tipo a IEnumerable<T> , puede hacer esto:

List<AMQuestion> typed = new List<AMQuestion>(); IEnumerable<IQuestion> nonTyped = typed;

Esto no hace ninguna conversión en la lista.

La razón por la que no puede convertir una List<AMQuestion> en una List<IQuestion> (asumiendo que AMQuestion implementa la interfaz) es que tendría que haber varias verificaciones de tiempo de ejecución en funciones como List<T>.Add , para asegurarse de que realmente estaba agregando una AMQuestion .