2010-09-15 14 views
22

Mam dwie klasy: klasę podstawową (Animal) i klasę wywodzącą się z (Cat). Klasa klasy zawiera jedną metodę wirtualną Zagraj, która zajmuje listę jako wejście parameter.Something jak tenLista Cast <> z klasy pochodnej na listę <> z klasy bazowej

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>()); 
     } 
    } 
} 

kiedy mogę skompilować powyższy program, pojawia się następujący błąd

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

Czy mimo to do osiągnięcia tego celu?

+3

możliwe duplikat [Casting kolekcji generycznych do typu bazowego] (http: //stackoverflow.com/questions/539287/casting-a-generic-collection-to-base-type) – finnw

+1

Pierwszą rzeczą do zrobienia jest zmiana argumentu Lista <> na argument IEnumerable <>. –

+0

możliwy duplikat [Lista konwersji <> obiektów klasy pochodnej do Listy <> obiektów klasy bazowej] (http://stackoverflow.com/questions/1817300/convert-list-of-derived-class-objects-to-list -of-base-class-objects) –

Odpowiedz

42

Powodem, dla którego nie można tego zrobić, jest fakt, że lista jest zapisywalna. Załóżmy, że były legalne, a co idzie źle:

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! 

Dobrze pies moich kotów, to jest zło.

Wymagana funkcja nazywa się "ogólna kowariancja" i jest obsługiwana w C# 4 dla interfejsów, o których wiadomo, że są bezpieczne. IEnumerable<T> nie ma żadnego sposobu zapisu w sekwencji, więc jest bezpieczny.

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>());  
    }  
} 

który będzie działał w C# 4, ponieważ List<Cat> jest zamienny do IEnumerable<Cat>, którą można przekształcić IEnumerable<Animal>. Nie ma możliwości, aby Play użył IEnumerable<Animal>, aby dodać psa do czegoś, co jest w rzeczywistości listą kotów.

+0

Tęsknię za tym, że to, że Play jest członkiem Animal, dodaje się do wyjaśnienia. Oznacza to, że gra przyjmuje IEnumerable . IMO byłoby czystsze, gdyby to był po prostu Play(), a przykład został umieszczony w void Main() przy użyciu przypisania. – Dykam

+1

@Dykam: Nie ma. Próbowałem podążać za strukturą kodu określoną przez oryginalny plakat. –

+0

Ach, rozumiem. Z jakiegoś powodu nie łączyłem tych dwóch fragmentów. – Dykam

10

Poszukujesz ogólnokrajowej kowariancji kolekcji. Oczywiście funkcja ta nie jest obsługiwana przez wersję C#, której używasz.

Możesz obejść to, używając metody przedłużania Cast<T>(). Bądź świadomy, chociaż, że to stworzy kopię oryginalnej listy zamiast przechodzenia oryginału jako inny typ:

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

W ogóle nie jest obsługiwany przez List, ponieważ jest typem ogólnym i wewnętrznym. – Dykam

+0

@Dykam - Czy mógłbyś to opracować? Komentarz nie ma większego sensu, jak jest. –

+0

Cóż, ponieważ zarówno List przyjmuje dane wejściowe, jak i zwraca dane wyjściowe, nie jest możliwa współwystępowanie i kontrawariancja. Jeśli byłbyś w stanie zrobić listę 'List list = new List ();' Następujące spowoduje awarię aplikacji: 'list.Add (new Elephant());'. – Dykam

3

wykorzystać Cast metodę rozszerzenia()

tak:

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

Powodem tego jest b/c .net 3.5 nie obsługuje kowariancji, ale 4.0 robi :)

+0

Powód jest wyłączony. 'cat.Play (nowa lista ());' również nie będzie działać w 4 z istniejącym kodem. –

13

Możesz zrobić kilka rzeczy. Jednym z przykładów jest oddanych elementy listy do Animal

przy użyciu kodu:

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

Innym jest, aby Animal rodzajowy, więc cat.Play(new List<Cat>()); będzie dział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>()); 
    } 
} 

Inną metodą jest nie zrobić Animal rodzajowy, ale metoda Play i ograniczyć to do 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) 
    { 
    } 
} 

Wreszcie, jeśli jesteś w C# 4 i tylko trzeba wymienić na listy i nie zmieniaj go, sprawdź odpowiedź Erica Lipperta na IEnumerable<Animal>.

+0

+1 dla ogólnej sztuczki –

+0

Także +1 dla wskazania, że ​​powinieneś używać IEnumerable dla parametru typ zamiast Listy –

+0

Nie rozumiem tego ostatniego bitu; dlaczego klasa Cat ma metodę z parametrem typu o nazwie Cat? Czy nie jest to bardzo mylące, aby mieć dwa typy o tym samym nazwisku w ramach tej samej deklaracji? –

2

Wszyscy już wymieniają metodę rzutowania. Jeśli nie można uaktualnić do 4,0 drogę do ukrycia obsady jest

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

Jest to ta sama sztuczka IEnumable i IEnumarable<T> zabaw dla GetEnumerator

Powiązane problemy