2009-03-11 12 views
5

Niedawno wpadł w kłopoty, gdy próbuje AddRange (IEnumerable) do listy. Prawdopodobnie klasyczny problem, ale tak naprawdę go jeszcze nie dostaję.C# Nie można przekonwertować z IEnumerable <Base> do IEnumerable <Derived>

Rozumiem, że metody oczekujące parametru List nie są usatysfakcjonowane Listą, ponieważ mogą próbować dodać Bazę do Listy, co oczywiście jest niemożliwe.

Ale jeśli mam to poprawnie, ponieważ IEnumerables nie mogą być zmienione, powinno to działać w tym przypadku.

Kod myślałem wygląda następująco:

class Foo 
{ 
} 

class Bar : Foo 
{ 
} 

class FooCol 
{ 
    private List<Foo> m_Foos = new List<Foo>(); 

    public void AddRange1(IEnumerable<Foo> foos) 
    { 
     m_Foos.AddRange (foos); // does work 
    } 

    public void AddRange2<T>(IEnumerable<T> foos) where T : Foo 
    { 
     m_Foos.AddRange (foos); // does not work 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     FooCol fooCol = new FooCol(); 

     List<Foo> foos = new List<Foo>(); 
     List<Bar> bars = new List<Bar>(); 

     fooCol.AddRange1 (foos); // does work 
     fooCol.AddRange1 (bars); // does not work 

     fooCol.AddRange2 (foos); // does work 
     fooCol.AddRange2 (bars); // does work 
    } 
} 

starałem się przekazać wskazówkę do kompilatora w metodzie AddRange2, ale to po prostu przeniósł się do problemu dookoła.

Czy mój sposób myślenia jest wadliwy? Czy jest to ograniczenie języka, czy jest to z założenia?

IIRC, wsparcie dla tego rodzaju operacji zostało dodane do Java 1.5, więc może będzie dodane do C# w pewnym momencie w przyszłości, zbyt ...?

+0

Duplikat różnych innych pytań, w tym ostatnio http://stackoverflow.com/questions/632399 –

+0

Rzeczywiście, przepraszam. Moje google-jutsu wciąż wymaga poprawy. – mafu

+0

Bez problemu. Byłoby miło mieć FAQ na temat tagów, IMO ... –

Odpowiedz

18

To jest kowariancja i zostanie naprawiony w C# 4.0/.NET 4.0. Na razie opcja ogólna jest najlepszą odpowiedzią (dla IEnumerable<T> - not IList<T> etc).

Ale w ramach metody ogólnej, musisz myśleć w kategoriach T. Możesz również użyć Cast<T> lub OfType<T> z LINQ, aby osiągnąć coś podobnego.

+1

Dziękuję za nowy termin, kowariancja. Nie wiedziałem o istnieniu. – Sung

0

Istnieje obejście metodą wydłużania:

public static IEnumerable<TBase> ToBaseEnumerable<TBase, TDerived>(this IEnumerable<TDerived> items) where TDerived : TBase { 
    foreach(var item in items) { 
     yield return item; 
    } 
} 
... 
IEnumerable<Employee> employees = GetEmployees(); //Emplyoee derives from Person 
DoSomethingWithPersons(employees.ToBaseEnumerable<Person, Employee>()); 

ale "< Osoba, Pracownik>" jest trochę niewygodne: /.

+0

Obsada () byłaby prostsza ... –

2

W języku C# 3.0 można użyć metody "Cast". Jeśli importujesz System.Linq i używasz tego kodu:

public void AddRange2<T>(IEnumerable<T> foos) where T : Foo 
{ 
    m_Foos.AddRange (foos.Cast<Foo>()); 
} 

To powinno działać dla Ciebie.

0

Rozwiązanie to może generować wyjątki odlewów klas. Osoba, która zamieściła przeliczalne rozszerzenie, powiedziała, że ​​było to niezręczne. wymyśliłem rozwiązanie, które jest tylko w połowie tak niewygodne, nie wiem, czy będę go używać:

public static class UpTo<T> 
{ 
    public static IEnumerable<T> From<F>(IEnumerable<F> source) where F:T 
    { 
     // this cast is guaranteed to work 
     return source.Select(f => (T) f); 
    } 
} 

Zastosowanie:

IEnumerable ssaki = września <Ssak> .Z (hodowla. Psy)

Powiązane problemy