2017-07-27 55 views
41

mam skonfigurować tak:nie można przekonwertować z listy <Bar> do listy <Foo>

abstract class Foo {} 
class Bar : Foo {} 

i sposób gdzie indziej tej postaci:

void AddEntries(List<Foo>) {} 

próbuję wywołać tę metodę za pomocą lista obiektów typu Bar

List<Bar> barList = new List<Bar>() 
AddEntries(barList); 

ale to daje mi błąd:

cannot convert from List<Bar> to List<Foo>

Czy w ogóle istnieje ten problem? Muszę zachować definicję metody za pomocą klasy abstrakcyjnej.

+1

Czy w C# mają składnię jak 'List 'jak robi to Java? – dbush

+2

@dbush Tak, wersja C# jest w zaakceptowanej odpowiedzi – Harrichael

+4

@harrichael Chociaż w przeciwieństwie do Java, C# obsługuje ogólną kowariancję, więc istnieje jeszcze lepsza opcja (patrz odpowiedź Roba) –

Odpowiedz

64

Można zrobić swój AddEntries rodzajowy i zmienić go na tym

void AddEntries<T>(List<T> test) where T : Foo 
{ 
    //your logic 
} 

Wystarczy popatrzeć na Constraints on Type Parameters celu uzyskania dalszych informacji.

60

Mają lektury Covariance and Contravariance in Generics from docs.microsoft.com jak ten, szczególnie sekcji „generyczne Interfejsy z kowariantna parametrów typu” pokryje się scenariusz, że pracujesz.

W skrócie, jeśli (może) zmienić podpis metodę AddEntries do:

static void AddEntries(IEnumerable<Foo> entries) 

(Uwaga wykorzystania IEnumerable<Foo> zamiast List<Foo>) będziesz w stanie zrobić to, czego szukasz do zrobienia.

Specyficzny Różnica polega na tym, że IEnumerable<T> i List<T> deklarowane są różnie:

public interface IEnumerable<out T> : IEnumerable 
public class List<T> : IList<T>, ... ... 

Różnica że liczy jest out w deklaracji IEnumerable<T> który wskazuje, że jest kowariantna co oznacza (z definicją zaczerpniętą z docs.microsoft.com):

Covariance: Enables you to use a more derived type than originally specified.

+1

Biorąc pod uwagę, że nazwa metody to AddEntries, jest mało prawdopodobne, aby OP mógł korzystać z IEnumerable. – Taemyr

+0

@Taemyr OP może jednak zdefiniować "interfejs". – wizzwizz4

+7

@Taemyr: Zakładam, że 'AddEntries' oznacza" Dodaj te wpisy do innej listy ", a nie" Dodaj wpisy do tej listy ". W przeciwnym razie żadna odpowiedź nie zadziała, ponieważ nie może dodać 'FooChild' do' List 'niezależnie. –

43

To nie jest dozwolone z prostego powodu. Załóżmy, że poniżej kompiluje:

AddEntries(new List<Bar>()); 

void AddEntries(List<Foo> list) 
{ 
    // list is a `List<Bar>` at run-time 
    list.Add(new SomethingElseDerivingFromFoo()); // what ?! 
} 

Jeśli kod będzie kompilować, masz niebezpieczny dodatek (co sprawia, że ​​cały list generyczne bezcelowe), gdzie dodany SomethingElseDerivingFromFoo do rzeczywistości List<Bar> (typ wykonawczego).

Aby rozwiązać problem, można użyć rodzajowych:

void AddEntries<T>(List<T> list) where T:Foo 
{ 

} 
+3

Kciuki za wyjaśnienie * dlaczego * nie można wykonać obsady, a nie tylko pokazanie, jak to działa. –

+0

Nie rozumiem. To jest [całkowicie poprawny kawałek kodu] (http://ideone.com/xoNJX8). Po prostu nie można polegać na żadnej dodatkowej funkcjonalności zapewnionej w klasie pochodnej (bez jawnego przesyłania). – Phylogenesis

+1

@Phylogenesis Nie można przekazać 'List ' do 'AddEntries', ponieważ jeśli dodałeś' SomethingElseDerivingFromFoo' do tej listy, byłoby to dziwne zachowanie (Masz element 'SomethingElseDerivingFromFoo' w' List '. Kod, który napisałem, idealnie się kompiluje – user3185569