2010-07-27 15 views
5

Descubrí que una lista de objetos concretos no se puede agregar a una lista de objetos de interfaz.Lista <IJob> .AddRange (Lista <Job>) No funciona

public static void AddJob(List<IJob> masterJobs, List<Job> jobs) 
{ 
    masterJobs.AddRange(jobs); //fail to compile 
} 

En cambio, hay que utilizar el siguiente código:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs) 
{ 
    masterJobs.AddRange(jobs.Cast<IJob>()); 
} 

¿Cuál es el racional detrás de esto?

+5

Skeet + covarianza/contravarianza en tres ... dos ... uno ... – Will

+0

Funcionará si puede usar C# 4.0 –

+1

Voy a suponer que el comentario '// no se puede compilar' el segundo ejemplo anterior es sobrante del primero? ¿O eso tampoco se puede compilar? –

Respuesta

10

Lasse tiene razón acerca de por qué esto no va a funcionar en C# 3 - no hay conversión de List<IJob> a List<Job>.

En C# 4 funcionará, no porque la lista sea covariante, sino porque IEnumerable<T> es covariante. Así, en otras palabras, el código sería efectivamente:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs) 
{ 
    IEnumerable<IJob> jobsTmp = jobs; // This is the covariance working 
    masterJobs.AddRange(jobs); // This is now fine 
} 

jobs implementa IEnumerable<Job>, así que hay una conversión de referencia a través de covarianza IEnumerable<IJob>, así que todo funciona bien. La llamada al Cast<T> está haciendo un trabajo similar en su solución C# 3, la está usando para convertir a IEnumerable<IJob>.

Si desea obtener más información sobre la variación genérica, hay un video of my NDC 2010 talk disponible, o lea el series of blog posts de Eric Lippert en él.

+0

Así que, permítanme aclarar esto ... usaron 'in' y' out' para significar contra/covarianza porque esencialmente describe que la clase * solo * "capta" una instancia del tipo genérico (es decir, un método argumento) o si la clase * solo * "pasa" una instancia de la clase (es decir, el valor de retorno de un método)? IEnumerable no "acepta" ninguna T, por lo que es seguro marcar la clase como y, por lo tanto, el tipo genérico es covariante. IList ambos toman T y pasan T, lo que lo hace invariante? – Will

+0

@Will: Sí, eso es exactamente correcto. Vea la serie de blogs de Eric Lippert sobre la varianza para obtener mucha más información. Editaré mi publicación para vincularla. –

+0

¡Por George, creo que lo tengo! ¡Creo que lo tengo! (número musical de cue) – Will

8

La razón es que un List<IJob> no es un List<Job>, aunque un Job implementa IJob.

Ésta es co- o contra-varianza (. No recuerdo cuál es cuál)

El pensamiento es algo como esto:

El compilador no puede garantizar que AddRange sólo lee las cosas del parámetro se se da, y por lo tanto no puede garantizar que esto sea seguro, y por lo tanto no compila.

Por ejemplo, a pesar de que el compilador sabe, AddRange podría agregar otro objeto en el parámetro jobs, que implementa IJob (porque AddRange espera IJob colecciones), pero no es Job, que es lo que jobs esperar, y por lo tanto que se no estar seguro.

En C# 4.0, hay algo de soporte para manejar esto, pero no estoy seguro de que manejaría su caso particular ya que el soporte debe especificarse en el nivel de interfaz, y no en el nivel de método.

En otras palabras, debería especificar en el tipo de interfaz que todo lo que se relaciona con T solo va a la colección, nunca se sale de ella, y luego el compilador le permite hacer esto. Sin embargo, una colección que no puede leer sería bastante inútil.

+1

"out" es covarianza y "in" es contravarianza; ahora es 'IList ' y, por lo tanto, es covariante. (y estoy equivocado en eso, mira a continuación) – Will

+0

Ooh, bien, no creo que haya visto ese breve resumen de los dos antes, ¡gracias! –

+0

Este tipo entra en buen detalle sobre la compatibilidad de genéricos de covarianza y contravarianza en 4.0: http://www.buunguyen.net/blog/new-features-of-csharp-4.html – Will

Cuestiones relacionadas