2011-06-01 20 views
6

En general, entiendo las interfaces, la herencia y el polimorfismo, pero una cosa me tiene desconcertado.IList <T> y List <T> Conversiones con interfaces

En este ejemplo, gato implementa IAnimal y por supuesto Lista implementa IList :

IList<IAnimal> cats = new List<Cat>(); 

pero genera un error de compilación (No se puede convertir implícitamente el tipo ...) . Tampoco funcionará si utilizo una superclase de asbtract [Animal] de la que Cat hereda. Sin embargo, si sustituyo IAnimal con gato:

IList<Cat> cats = new List<Cat>(); 

que compila bien.

En mi mente, porque gato implementa IAnimal, el primer ejemplo debería ser aceptable, lo que nos volvemos a una interfaz tanto para la lista y el tipo de contenido.

¿Alguien puede explicar por qué no es válido? Estoy seguro de que hay una explicación lógica.

Respuesta

20

Hay una explicación lógica, y esta pregunta exacta se realiza casi todos los días en StackOverflow.

supongo que esto era legal:

IList<IAnimal> cats = new List<Cat>(); 

¿Qué impide que esto sea legal?

cats.Add(new Giraffe()); 

Nada. "gatos" es una lista de animales, y una jirafa es un animal, y por lo tanto puede agregar una jirafa a una lista de gatos.

Está claro que no es seguro.

En C# 4 agregamos una característica por la cual puedes hacer eso si las anotaciones de metadatos permiten que el compilador demuestre que es seguro. En C# 4 se puede hacer esto:

IEnumerable<IAnimal> cats = new List<Cat>(); 

porque IEnumerable<IAnimal> tiene ningún método Add, por lo que no hay manera de violar la seguridad de tipos.

Consulte mi serie de artículos sobre cómo diseñamos esta función en C# 4 para obtener más detalles.

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

(comienzo a partir de la parte inferior.)

+2

+1: informativo y útil como de costumbre :) – Juliet

+0

Gracias Eric. Definitivamente estaré leyendo tus artículos. También me disculpo por hacer una pregunta constantemente repetida también ... haré una mejor búsqueda la próxima vez. – Curtmantle

+2

@Mark: De nada, y sin preocupaciones; el hecho de que esta pregunta se haga tanto es una de las cosas que motivó agregar la función a C# 4 en primer lugar. Claramente, las personas tienen la intuición de que la varianza genérica debe ser parte del sistema de tipos. Ahora solo se trata de educar a las personas sobre qué tipos de varianza son probablemente seguros. –

1

Este tipo de covariance no es compatible con C# 4.0. Es razonable esperar el comportamiento que desea, simplemente no es compatible (por ahora).

+2

¿Puedes aclarar? La covarianza y la contravarianza se admiten en C# 4.0, pero IList no es una interfaz covariante. –

+0

Jaja, bueno, Eric Lippert acaba de responder, así que creo que eso lo resume todo: D –

1

se puede lograr que el uso de LINQ:

IList<IAnimal> cats = new List<Cat>().Cast<IAnimal>(); 
6

C# no es compatible con este tipo de variación en IList<T> por razones de seguridad de tipos.

Si C# no fue compatible con esto, ¿qué esperarías que ocurriera aquí?

IList<IAnimal> cats = new List<Cat>(); 

cats.Add(new Dog());   // a dog is an IAnimal too 
cats.Add(new Squirrel()); // and so is a squirrel 

En C# 4 que es capaz de hacer algo como esto:

IEnumerable<IAnimal> cats = new List<Cat>(); 

Esto se debe a que la interfaz IEnumerable<T> hace de soporte varianza de este tipo. Un IEnumerable<T> es una secuencia de solo lectura, por lo que no hay manera de que posteriormente pueda agregar un Dog o un Squirrel a un IEnumerable<IAnimal> que en realidad es una lista de Cat.

1

IList<T> no es una interfaz covariante (o sería IList<out T>). Esto se debe a que IList toma el tipo T como parámetro y lo devuelve como un valor de retorno de los métodos, lo que hace que la covarianza sea problemática.

Por ejemplo, si en su ejemplo:

IList<IAnimal> cats = new List<Cat>();

desea agregar un nuevo gato, de los gatos, que permitiría:

cats.Add(new Dog());

asumiendo perro también implementado IAnimal , esto es obviamente incorrecto y no funcionaría. Por eso IList no es una interfaz covariante o contravariante.

0

Si necesita covarianza y contravarianza en una interfaz de lista como, debe definir una interfaz IReadableList < cabo T > y IWritableList < en T >, y se derivan de un tipo de la lista <T> que implementa tanto ReadableList < T & y WriteableList <T>. Esto permitiría pasar una NewList <Cat> a una rutina que espera una ReadableList <Animal> o una WritableList <SiameseCat>.

Cuestiones relacionadas