2013-06-06 15 views
24

Próbuję przekazać listę DerivedClass do funkcji, która pobiera listę BaseClass, ale pojawia się błąd:nie można przekonwertować z List <DerivedClass> do listy <BaseClass>

cannot convert from 
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>' 
to 
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>' 

Teraz mogę rzucić My List<DerivedClass> do List<BaseClass>, ale nie czuję się komfortowo, że chyba rozumiem dlaczego kompilator nie pozwala na to.

Wyjaśnienia, że ​​znalazłem się po prostu powiedział, że narusza bezpieczeństwo typu jakoś, ale nie widzę go. Czy ktoś może mi pomóc?

Jakie ryzyko wiąże się z kompilatorem umożliwiającym konwersję z List<DerivedClass> na List<BaseClass>?


Oto mój SSCCE:

class Program 
{ 
    public static void Main() 
    { 
     BaseClass bc = new DerivedClass(); // works fine 
     List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error 

     doSomething(new List<DerivedClass>()); // this line has an error 
    } 

    public void doSomething(List<BaseClass> bc) 
    { 
     // do something with bc 
    } 
} 

class BaseClass 
{ 
} 

class DerivedClass : BaseClass 
{ 
} 
+0

Klasa wywodzi się od jego podstawy, ale nie '' Lista wywodzi się od '' Lista . –

+0

@TimSchmelter Czy to oznacza, że ​​tego rodzaju dziedziczenie może potencjalnie stać się funkcją, w której 'Generic ' może być uważana za pochodną 'Generic '? lub czy taka funkcja może powodować problemy? –

+1

Po prostu nie jest to realizowane w ten sposób. http://marcgravell.blogspot.fr/2009/02/what-c-40-covariance-doesn-do.html –

Odpowiedz

37

To dlatego List<T> jest in-variant nie co-variant, więc należy zmienić IEnumerable<T> który obsługuje co-variant, to powinno działać:

IEnumerable<BaseClass> bcl = new List<DerivedClass>(); 
public void doSomething(IEnumerable<BaseClass> bc) 
{ 
    // do something with bc 
} 

Informacja około co-variant in generic

+0

Począwszy od .Net 4.5, istnieje również 'IReadOnlyList ', który może być użyty, jeśli metoda wymaga losowego dostępu do elementu kolekcji. – tia

11

zachowanie jesteś opisujące nazywa kowariancji – jeśli AjestB, następnie List<A>jestList<B>.

Jednak dla typy modyfikowalnych jak List<T>, że jest fundamentalnie niebezpieczne.

Gdyby było to możliwe, metoda byłaby w stanie dodać new OtherDerivedClass() do listy, która może w rzeczywistości zawierać tylko DerivedClass.

kowariancji jest bezpieczny na niezmiennych typów, chociaż Net obsługuje go tylko w interfejsach i delegatów.
Po zmianie parametru List<T> do IEnumerable<T>, że będzie działać

+0

@SamIam: Nie; próbujesz przekazać 'List '. Przekazywanie "Listy " zawierającej "Pochodne" powinno działać dobrze. – SLaks

+0

Więc jeśli wyrzuciłem lub przekonwertowałem moją 'List ' na 'List ' przed przekazaniem jej byłoby to w porządku? –

+1

@SamIam: Nie. Chodzi o to, że zasadniczo nie jest bezpieczne umieszczanie 'List ' na 'List '. Jeśli utworzysz 'List ' na początek, zadziała. – SLaks

1

Gdybyś mógł napisać:

List<BaseClass> bcl = new List<DerivedClass>(); 

można nazwać

var instance = new AnotherClassInheritingFromBaseClass(); 
bc1.Add(instance); 

Dodawanie instancji, który nie jest DerivedClass do listy.

2

Kiedy masz klasę pochodzącą z klasy bazowej, wszystkie pojemniki z tych klas nie są automatycznie pochodzi. Więc nie można po prostu rzucić List<Derived> do List<Base>.

Zastosowanie .Cast<T>() aby utworzyć nową listę, gdzie każdy przedmiot jest odlewana z powrotem do klasy bazowej:

List<MyDerived> list1 = new List<MyDerived>(); 
List<MyBase> list2 = list1.Cast<MyBase>().ToList(); 

pamiętać, że jest nowa lista, a nie wersja odlew swojej pierwotnej liście, więc operacje na ten nowa lista nie będzie odzwierciedlona na pierwotnej liście. Operacje na zawartych obiektach będą jednak odzwierciedlać.

+1

'List list2 = nowa lista (lista1);' uderza mnie bardziej czystym niż używanie 'Cast'. – Brian

38

Explanations that I have found have simply said that it violates type safety somehow, but I'm not seeing it. What is the risk of the compiler allowing conversion from List<DerivedClass> to List<BaseClass> ?

To pytanie jest zadawane prawie codziennie.

List<Mammal> nie może zostać przekształcony w List<Animal> ponieważ można umieścić jaszczurkę na listę zwierząt. A List<Mammal> nie można przekonwertować na List<Giraffe> ponieważ na liście może być już tygrys.

Dlatego List<T> musi być niezmienna T.

Jednakże List<Mammal> można przekształcić w IEnumerable<Animal> (w C# 4.0), ponieważ nie ma sposobu, który dodaje się na IEnumerable<Animal> jaszczurki. IEnumerable<T> jest kowariantna w T.

+4

+1 Proste wyjaśnienie zoologiczne, które wyjaśnia wszystko. (nie ma metody na IEnumerable , która dodaje jaszczurkę -> sprawdzę na wszelki wypadek;)) –

+0

Czuję się głupio ... żyrafa wydaje się oczywista, ale dlaczego nie powinieneś móc przekazywać ssakom coś, co poradzi sobie ze zwierzętami, tylko dlatego, że poradzi sobie także z ssakami? – MGOwen

+2

@MGOwen: Masz listę ssaków. Postanawiasz traktować to jako listę zwierząt. Umieszczasz jaszczurkę na liście zwierząt. ** Ale to naprawdę lista ssaków **. Teraz masz listę ssaków z jaszczurką, która narusza oczekiwanie, że lista ssaków zawiera tylko ssaki. Wniosek jest naruszeniem systemu typu, więc jeden z tych kroków musi być nielegalny. Jest to pierwsza: nie można traktować listy ssaków jako listy zwierząt. –

0

Rozwiązanie Kiedyś było stworzenie klasy rozszerzenia:

public static class myExtensionClass 
{ 
    public void doSomething<T>(List<BaseClass> bc) where T: BaseClass 
    { 
     // do something with bc 
    } 
} 

Wykorzystuje uniwersalne, ale kiedy to nazwać nie masz do EZa klasę od ciebie już "powiedział" kompilatorowi, że typ jest taki sam dla rozszerzonej klasy.

nazwałbyś to w ten sposób:

List<DerivedClass> lst = new List<DerivedClass>(); 
lst.doSomething(); 
Powiązane problemy