2010-10-27 15 views
18

¿Es posible emitir List<Subclass> a List<Superclass> en C# 4.0?covarianza en C#

Algo a lo largo de estas líneas:

class joe : human {} 

List<joe> joes = GetJoes(); 

List<human> humanJoes = joes; 

No es esto lo covarianza es para?

si se puede hacer:

human h = joe1 as human; 

por qué no se deben poder hacer

List<human> humans = joes as List<human>; 

de lo que no sería legal de hacerlo (Joe) los seres humanos [0] porque eso el objeto se ha caído ... y todos estarían felices. Ahora, la única alternativa es crear una nueva lista

+2

Este es básicamente el mismo que [En C#, ¿por qué no se puede almacenar un objeto de lista en una variable de lista ] (http://stackoverflow.com/questions/6557/in-c-porqué-cant-a-liststring-object-be-stored-in-a-listobject-variable). –

+0

porque 'humans' se estaría refiriendo entonces a una instancia de' List ', que causaría problemas como se ilustra en el ejemplo de @ Jon. –

+0

sí, después de que corrigió el ejemplo lo obtuve ... tiene sentido –

Respuesta

25

No se puede hacer esto, porque no sería seguro. Considere:

List<Joe> joes = GetJoes();  
List<Human> humanJoes = joes; 
humanJoes.Clear(); 
humanJoes.Add(new Fred()); 
Joe joe = joes[0]; 

Es evidente que la última línea (si no una anterior) tiene a fallar - como Fred no es un Joe. La invariancia de List<T> previene este error en compila tiempo en lugar de tiempo de ejecución.

+0

así HumanJoes es una lista de Humanos, por lo que no debería intentar (o poder) sacar un Joe de él. pero cada joe es un ser humano, por lo que debería ser seguro lanzar todo a la subclase de forma segura. –

+0

ciertamente puedes hacer Human h = joesList [0] como Humano ... y eso es lo que estaba buscando/preguntando .. aceptar en un nivel de lista completo –

+0

@Sonic: ¿Por qué no agregarías un 'Joe' extra? o 'Fred' para eso? * Ese * tipo de variación es * siempre * aceptable. Pruébalo: 'List list = new List (); list.Add ("esto es una cadena)"; '. ¿Por qué esperarías que fallara? –

2

No. Las características de co/contravariación de C# 4.0 solo admiten interfaces y delegados. No es compatible con tipos de hormigón como List<T>.

+0

http://stackoverflow.com/questions/245607/how-is-generic-covariance-contra-variance-implemented-inc-c-4-0 –

+0

entonces ¿tendría que recorrer y crear un nuevo objeto para cada joe como humano y agregarlo a la nueva lista? esa es la única opción? –

+1

No es posible con 'IList ', porque eso es invariable. –

6

instanciar una nueva lista de humanos en la cual los joes como entrada:

List<human> humanJoes = new List<human>(joes); 
1

No. Como dijo Jared, las características de co/contravariación de C# 4.0 solo admiten interfaces y delegados. Sin embargo, tampoco funciona con IList<T>, y la razón es que IList<T> contiene métodos para agregar y cambiar elementos en la lista, como dice la nueva respuesta de Jon Skeet.

La única manera de ser capaz de lanzar una lista de "Joe" a "humano" es si la interfaz es puramente de sólo lectura por diseño, algo como esto:

public interface IListReader<out T> : IEnumerable<T> 
{ 
    T this[int index] { get; } 
    int Count { get; } 
} 

Incluso un método Contains(T item) haría no se permite, porque cuando echas IListReader<joe> a IListReader<human>, no hay un método Contains(human item) en IListReader<joe>.

Se puede "forzar" un elenco de IList<joe> a IListReader<joe>, IListReader<human> o incluso usando un IList<human>GoInterface. Pero si la lista es lo suficientemente pequeña como para copiarla, una solución más simple es simplemente copiarla en una nueva List<human>, como señaló Paw.

0

Si permito que su List<Joe> joes a generalizar como ...

List<Human> humans = joes; 

... las dos referencias humans y joes son, ahora en adelante, apuntando a la misma lista exacta. El código que sigue a la asignación anterior no tiene forma de evitar la inserción/adición de una instancia de otro tipo de humano, digamos un Plomero, en la lista.Dado que class Plumber: Human {}

humans.Add(new Plumber()); // Add() now accepts any Human not just a Joe 

la lista que humans refiere a contiene ahora ambos joes y fontaneros. Tenga en cuenta que el mismo objeto de la lista todavía se refiere a la referencia joes. Ahora, si uso la referencia joes para leer desde el objeto de la lista, es posible que aparezca un plomero en lugar de un joe. No se sabe que Fontaner y Joe sean implícitamente intercambiables ... así que al obtener un plomero en lugar de un joe de la lista se rompe la seguridad del tipo. Un fontanero ciertamente no es bienvenido a través de una referencia a una lista de joes.

Sin embargo, en las versiones recientes de C#, es posible evitar esta limitación para una clase/colección genérica al implementar una interfaz genérica cuyo parámetro de tipo tiene un modificador out. Digamos que ahora tenemos ABag<T> : ICovariable<out T>. El modificador de salida restringe la T a las posiciones de salida solamente (por ejemplo, tipos de retorno de método). No puedes ingresar ninguna T en la bolsa. Solo puedes leerlos. Esto nos permite generalizar joes a un ICovariable<Human> sin preocuparse de insertar un plomero en él ya que la interfaz no lo permite. Ahora podemos escribir ...

ICovariable<Human> humans = joes ; // now its good ! 
humans.Add(new Plumber()); // error 
Cuestiones relacionadas