2013-07-30 21 views
10

Dlaczego kompilator C# nie zezwala na parametry polimorficzne (T) w kolekcjach ogólnych (np. Lista [T])?Polimorficzne parametry typów w kolekcjach ogólnych

Weź klasy 'A' i 'B' na przykład, gdy 'B' jest podklasą 'A'

class A { } 
class B : A { } 

i rozważyć funkcję, która pobiera listę typu 'A'

void f(List<A> aL) { } 

że jest wywoływana z listy typu 'B'

List<B> bL = new List<B>(); 

f(bL); 

następujący błąd jest dany

ERROR: cannot convert from List<B> to List<A> 

Jaka zasada semantyczna jest naruszana?

Czy istnieje również "elegancki" środek do tego celu, poza zapętlaniem i odlewaniem każdego elementu (proszę o trochę cukru)? Dzięki.

+4

"Co semantyczny zasada jest łamane?" - zasada zastąpienia Liskov. – millimoose

+0

Jaką wersję programu Visual Studio, C#? – Rake36

+0

@ Rake36 Nie powinno mieć znaczenia, co próbuje OP nie jest po prostu bezpieczne. – millimoose

Odpowiedz

10

List<B> po prostu nie jest podtypem List<A>. (Nigdy nie jestem pewien, o co „kowariantna”, a co „kontrawariantny” jest w tym kontekście tak będę trzymać z „podtyp”). Rozważmy przypadek, gdzie można to zrobić:

void Fun(List<A> aa) { 
    aa(new A()); 
} 

var bb = new List<B>(); 
Fun(bb); // whoopsie 

Jeśli to, co chcesz aby to zrobić, możliwe jest dodanie A do listy B s, która z pewnością nie jest bezpieczna dla typu.

Teraz wyraźnie to możliwe czytać elementy z listy bezpiecznie, dlatego C# umożliwia tworzenie covariant (i.e. "read-only") interfaces - co pozwoli kompilator wie, że nie ma możliwości, aby spowodować tego rodzaju korupcji przez nich. Jeśli potrzebujesz tylko do odczytu, na zbiorach, zwykle jeden jest IEnumerable<T>, więc w Twoim przypadku może po prostu zrobić metodę:

void Fun(IEnumerable<A> aa) { ... } 

i użyć metod Enumerable - najbardziej powinny być zoptymalizowane, jeśli typ bazowy jest List .

Niestety, ze względu na sposób działania elementów generycznych języka C#, klasy nie mogą być w ogóle wariantami, a jedynie interfejsami. I o ile mi wiadomo, wszystkie interfejsy kolekcji "bogatsze" niż IEnumerable<T> to "odczyt i zapis". Można technicznie stworzyć własny interfejs opakowania kowariancyjnego, który eksponuje jedynie potrzebne operacje odczytu.

0

Twoje pytanie jest bardzo podobne do mine: odpowiedź brzmi, że nie można wykonać tego rzutu, ponieważ te typy są tworzone przez klasę szablonów i nie dziedziczą. co możesz zrobić:

f(bL.Cast<A>()); 
+0

+1 Twoje "różne typy" wyjaśnień daje dokładny powód, dlaczego to nie działa, więc dziękuję, i dobra składnia, spróbuję. – samosaris

+0

@samusArin wow, nigdy nie widziałem, żeby ktoś wkładał tak wiele wysiłku w komentowanie każdej odpowiedzi! tylko po to przeszukuję każdą dobrą odpowiedź, którą dałeś i dajesz im +1. Nadążaj za dobrą robotą. –

+0

Dziękuję, ale tak naprawdę nie musiałaś ... Robię to b/c. Doceniam wszystkie pomocne odpowiedzi, mimo że mogę je tylko zaakceptować. Wydaje mi się, że jest cicho, nie zwracając uwagi na ludzi, którzy ci pomagają, nie wspominając o ich pomocy i wysiłkach !! – samosaris

11

Weź ten mały przykład, dlaczego to nie może działać. Wyobraźmy sobie, że masz inny podtyp C z A:

class A {} 
class B : A {} 
class C : A {} 

Wtedy oczywiście, mogę umieścić C obiekt na liście List<A>. Ale teraz wyobrazić następującą funkcję biorąc A-listy:

public void DoSomething (List<A> list) 
{ 
    list.Add(new C()); 
} 

Jeśli zdać List<A> to działa zgodnie z oczekiwaniami, ponieważ C jest prawidłowy typ umieścić w List<A>, ale jeśli zdać List<B>, to nie można umieść na tej liście C.

Ogólny problem, który występuje tutaj, patrz covariance and contravariance for arrays.

+0

Wielkie dzięki, to jest bardzo zwięzłe i jasne. – samosaris

2

Nie ma nic z natury niewłaściwego w przekazywaniu kolekcji B do metody, która oczekuje kolekcji A. Jest jednak wiele rzeczy, które mogą pójść nie tak, w zależności od tego, co zamierzasz zrobić z kolekcją.

Rozważmy:

void f(List<A> aL) 
{ 
    aL.(new A()); // oops! what happens here? 
} 

Oczywiście jest tu problem: jeśli aL mogły być List<B> wówczas realizacja spowodowałaby jakiś rodzaj błędu wykonawczego, albo na miejscu, albo (znacznie gorzej) czy później kod obsługuje instancję A, którą wstawiamy jako B.

Kompilator nie pozwala na użycie List<B> jako List<B> w celu zachowania bezpieczeństwa typu i zagwarantowania, że ​​kod nie będzie wymagał sprawdzenia w czasie wykonywania.Należy pamiętać, że takie zachowanie jest inne niż to, co (niestety) dzieje się z tablicami - decyzja projektanta język jest kompromis, a oni inaczej postanowił przy różnych okazjach:

void f(A[] arr) 
{ 
    arr[0] = new A(); // exception thrown at runtime 
} 

f(new B[1]); 
+0

Dziękuję Panu za głęboki wgląd w tę sprawę. – samosaris

1

myślę, że może być patrząc na „out” generic modyfikatory, które pozwalają na kowariancję między dwoma typami rodzajowymi.

http://msdn.microsoft.com/en-us/library/dd469487.aspx

Przykładem jak pisał na tej stronie:

// Covariant delegate. 
public delegate R DCovariant<out R>(); 

// Methods that match the delegate signature. 
public static Control SampleControl() 
{ return new Control(); } 

public static Button SampleButton() 
{ return new Button(); } 

public void Test() 
{    
    // Instantiate the delegates with the methods. 
    DCovariant<Control> dControl = SampleControl; 
    DCovariant<Button> dButton = SampleButton; 

    // You can assign dButton to dControl 
    // because the DCovariant delegate is covariant. 
    dControl = dButton; 

    // Invoke the delegate. 
    dControl(); 
} 

nie jestem pewien czy C# aktualnie obsługuje kowariancji dla swoich aktualnych kolekcjach.

+0

Doskonały przykład, powinno to bardzo pomóc, gdy mogę poświęcić trochę czasu na zgłębienie koncepcji konwolucji. Dzięki Kenogu. – samosaris

1

Jesteś błędem, że B dziedziczy z A; ale List<B> nie dziedziczy po List<A>. List<A> != A;

Można to zrobić:

List<A> aL = new List<A>(); 
aL.Add(new B()); 

f (aL) 

Można wykryć typ w void f(List<A> list)

foreach(A a in list) 
{ 
    if (a is B) 
    //Do B stuff 
    else 
    //Do A stuff 
} 
+0

Ahh, podejrzewałem to, ale nie lubię zaśmiecać moich pytań własnymi hipotetycznymi odpowiedziami, więc nie wspomniałem. Dziękuję Ci. – samosaris

Powiązane problemy