2010-01-09 8 views
44

Mira el siguiente ejemplo (parcialmente tomado de MSDN Blog):C# varianza problema: Asignación Lista <Derived> como Lista <Base>

class Animal { } 
class Giraffe : Animal { } 

static void Main(string[] args) 
{ 
    // Array assignment works, but... 
    Animal[] animals = new Giraffe[10]; 

    // implicit... 
    List<Animal> animalsList = new List<Giraffe>(); 

    // ...and explicit casting fails 
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>(); 
} 

¿Es un problema de covarianza? ¿Esto se admitirá en la futura versión de C# y existen algunas soluciones inteligentes (utilizando únicamente .NET 2.0)?

Respuesta

95

Bueno, esto ciertamente no se apoya en C# 4. Hay un problema fundamental:

List<Giraffe> giraffes = new List<Giraffe>(); 
giraffes.Add(new Giraffe()); 
List<Animal> animals = giraffes; 
animals.Add(new Lion()); // Aargh! 

Mantener jirafas segura: decir no a la varianza inseguro.

La versión de matriz funciona porque las matrices hacen varianza de tipo de referencia de soporte, con comprobación del tiempo de ejecución. El objetivo de los genéricos es proporcionar tiempo de compilación tipo de seguridad.

En C# 4 habrá compatibilidad con varianza genérica segura, pero solo para interfaces y delegados. Por lo que será capaz de hacer:

Func<string> stringFactory =() => "always return this string"; 
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4 

Func<out T>es covariante en T porque T se utiliza sólo en una posición de salida. Compare eso con Action<in T> que es contravariant en T porque T sólo se utiliza en una posición de entrada allí, haciendo de este seguro:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode()); 
Action<string> stringAction = objectAction; // Safe, allowed in C# 4 

IEnumerable<out T> es covariante, así, haciendo de esta correcto en C# 4, como se ha señalado por otros:

IEnumerable<Animal> animals = new List<Giraffe>(); 
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface. 

en términos de trabajar alrededor de este en su situación en C# 2, se necesitan para mantener una lista, o le gustaría ser feliz la creación de una nueva lista? Si eso es aceptable, List<T>.ConvertAll es tu amigo.

+0

+1 solo para agregar a la respuesta de Jon (no es que necesite ayuda), siguiendo el ejemplo 'Func ' al 'Func ' y el hecho de que 'T' es contravariante en' Action'. La siguiente 'Acción = Acción ' no funcionaría en C# 4. –

+0

Gracias por señalar el problema de seguridad del tipo. Eso es realmente útil. – AndiDog

+0

¿Qué pasa con 'animals.Add (new Lion()); // ¡Aargh! '? Si puedes lanzar un "León" y usarlo como un "Animal", y si estás utilizando todos los elementos en "animales" como "Animal", ¿cuál es el problema? – Jeff

11

Funcionará en C# 4 para IEnumerable<T>, por lo que puede hacer:

IEnumerable<Animal> animals = new List<Giraffe>(); 

Sin embargo List<T> no es una proyección covarient, por lo que no se puede asignar listas como lo ha hecho anteriormente, ya que podría hacer esto:

List<Animal> animals = new List<Giraffe>(); 
animals.Add(new Monkey()); 

Claramente no es válido.

6

En términos de List<T>, me temo que estás de suerte. Sin embargo, .NET 4.0/C# 4.0 agrega soporte para interfaces covariantes/contravariantes. Específicamente, IEnumerable<T> ahora se define como IEnumerable<out T>, lo que significa que el parámetro de tipo ahora es covariante.

Esto significa que puede hacer algo como esto en C# 4.0 ...

// implicit casting 
IEnumerable<Animal> animalsList = new List<Giraffe>(); 

// explicit casting 
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 

Nota: Los tipos de matriz también se han covariantes (al menos desde .NET 1.1).

Creo que es una pena que el soporte de varianza no se haya agregado para IList<T> y otras interfaces genéricas similares (o incluso clases genéricas), pero bueno, al menos tenemos algo.

+7

No se puede hacer IList covariante de forma segura con anotaciones de varianza del lado de la declaración. –

4

La covarianza/contravariancia no se puede admitir en colecciones mutables como otros han mencionado porque es imposible garantizar la seguridad de tipo en ambos sentidos en tiempo de compilación; Sin embargo, es posible hacer una conversión de una vía rápida en C# 3.5, si eso es lo que estás buscando:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = giraffes.Cast<Animal>().ToList(); 

Por supuesto que no es lo mismo, no es en realidad covarianza - que está en realidad crear otra lista, pero es una "solución alternativa", por así decirlo.

En .NET 2.0, puede tomar ventaja de la matriz de covarianza para simplificar el código:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = new List<Animal>(giraffes.ToArray()); 

pero tenga en cuenta que en realidad estás creando dos nuevas colecciones aquí.

+0

Creo que estas son soluciones bastante buenas para mi aplicación simple. Al menos son buenos para la legibilidad, no para el rendimiento. – AndiDog

+0

@JohnAskew: No estoy seguro de cuál es su punto aquí - no hay ningún método 'Cast' en' IList ', es el método de extensión' Enumerable .Cast ', que toma' IEnumerable 'y expulsa cada elemento a 'T2' y luego lo devuelve (como un' IEnumerable ', no un' IList '). No tiene nada que ver con 'IList ' en absoluto, salvo por el hecho de que 'IList ' pasa a heredar de 'IEnumerable ' y por lo tanto admite los métodos de extensión 'Enumerable '. No hay covarianza alguna allí. – Aaronaught

+0

Vaya ... Hice clic en eliminar en lugar de editar: -S –

Cuestiones relacionadas