2010-09-15 33 views
22

Tengo dos clases: una clase base (Animal) y una clase derivada de it (Cat). La clase Base contiene un método virtual Play que toma List como parameter.Something de entrada como estaCasting List <> de la clase derivada a List <> de la clase base

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication9 
{ 
    class Animal 
    { 
     public virtual void Play(List<Animal> animal) { } 
    } 
    class Cat : Animal 
    { 
     public override void Play(List<Animal> animal) 
     { 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Cat cat = new Cat(); 
      cat.Play(new List<Cat>()); 
     } 
    } 
} 

cuando compilo el programa anterior, me sale el siguiente error

 
    Error 2 Argument 1: cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.List' 

¿hay alguna forma de lograr esto?

+3

posible duplicado de [Casting una colección genérica de tipo base] (http: //stackoverflow.com/questions/539287/casting-a-generic-collection-to-base-type) – finnw

+1

Lo primero que hay que hacer es cambiar el argumento List <> a un argumento IEnumerable <>. –

+0

posible duplicado de [Convertir lista <> de objetos de clase derivados a List <> de objetos de clase base] (http://stackoverflow.com/questions/1817300/convert-list-of-derived-class-objects-to-list -of-base-class-objects) –

Respuesta

42

La razón por la que no puede hacer esto es porque una lista es de escritura. Supongamos que es legal, y vea qué va mal:

List<Cat> cats = new List<Cat>(); 
List<Animal> animals = cats; // Trouble brewing... 
animals.Add(new Dog()); // hey, we just added a dog to a list of cats... 
cats[0].Speak(); // Woof! 

Bueno, perrito, mis gatos, eso es maldad.

La función que desea se denomina "covarianza genérica" ​​y se admite en C# 4 para las interfaces que se sabe que son seguras. IEnumerable<T> no tiene forma de escribir en la secuencia, por lo que es seguro.

class Animal  
{  
    public virtual void Play(IEnumerable<Animal> animals) { }  
}  
class Cat : Animal  
{  
    public override void Play(IEnumerable<Animal> animals) { }  
}  
class Program  
{  
    static void Main()  
    {  
     Cat cat = new Cat();  
     cat.Play(new List<Cat>());  
    }  
} 

que trabajará en C# 4 List<Cat> porque se puede convertir en IEnumerable<Cat>, que es convertible a IEnumerable<Animal>. No hay forma de que Play pueda usar IEnumerable<Animal> para agregar un perro a algo que en realidad sea una lista de gatos.

+0

Me falta cómo el hecho de jugar como miembro de Animal se agrega a la explicación. Es decir, Play tomando un IEnumerable . IMO sería más claro si solo fuera Play() y el ejemplo se colocara en el vacío Main() usando la asignación. – Dykam

+1

@Dykam: No es así. Intentaba seguir la estructura del código presentada por el póster original. –

+0

Ah, ya veo. Por alguna razón, no vinculé los dos fragmentos. – Dykam

10

Está buscando la covarianza de la colección genérica. Obviamente, sin embargo, esa característica no es compatible con la versión de C# que estás usando.

Puede solucionar el problema utilizando el método de extensión Cast<T>(). Tenga en cuenta, sin embargo, que esto creará una copia de su lista original en lugar de pasar el original como un tipo diferente:

cat.Play((new List<Cat>()).Cast<Animal>().ToList()); 
+0

No es compatible con List, ya que es un genérico de entrada y salida. – Dykam

+0

@Dykam - ¿Puedes colaborar por favor? El comentario no tiene mucho sentido como es. –

+0

Bueno, como List toma entrada, también devuelve resultados, co y contravariancia no es posible. Si pudiera hacer 'List list = new List ();' Lo siguiente bloquearía la aplicación: 'list.Add (new Elephant());'. – Dykam

3

usar la conversión método de extensión()

manera:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Cat cat = new Cat(); 
     cat.Play(new List<Cat>().Cast<Animal>()); 
    } 
} 

La razón de esto es b/c .net 3.5 no admite la covarianza, pero 4.0 lo hace :)

+0

El motivo está desactivado. 'cat.Play (new List ());' tampoco funcionaría en 4 con el código existente. –

13

Podría hacer algunas cosas. Un ejemplo es arrojado los elementos de la lista para Animal

Utilizando su código:

cat.Play(new List<Cat>().Cast<Animal>().ToList()); 

Otra es hacer Animal genérica, por lo cat.Play(new List<Cat>()); funcionaría.

class Animal<T> 
{ 
    public virtual void Play(List<T> animals) { } 
} 
class Cat : Animal<Cat> 
{ 
    public override void Play(List<Cat> cats) 
    { 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Cat cat = new Cat(); 
     cat.Play(new List<Cat>()); 
    } 
} 

Otro método es no hacer Animal genérico, pero el método y Play restringir que a T : Animal

class Animal 
{ 
    public virtual void Play<T>(List<T> animals) where T : Animal { } 
} 
class Cat : Animal 
{ 
    public override void Play<T>(List<T> animals) 
    { 
    } 
} 

Por último, si usted está en C# 4 y sólo es necesario enumerar la lista y no lo modifique, consulte la respuesta de Eric Lippert en IEnumerable<Animal>.

+0

+1 para el truco genérico –

+0

También +1 para señalar que probablemente deba usar IEnumerable para el tipo de parámetro en lugar de List –

+0

No entiendo ese último bit; ¿Por qué la clase Cat tiene un método con un parámetro de tipo llamado Cat? ¿No es muy confuso, tener dos tipos con el mismo nombre dentro de la misma declaración? –

2

Todo el mundo menciona el método de lanzamiento ya. Si no puede actualizar a 4.0 una forma de ocultar el elenco es

class Cat : Animal 
{ 
    public override void Play(List<Animal> animal) 
    { 
     Play((List<Cat>)animal); 
    } 
    public virtual void Play(List<Cat> animal) 
    { 
    } 
} 

Este es el mismo truco IEnumable y IEnumarable<T> de juegos para GetEnumerator

Cuestiones relacionadas