2009-03-11 5 views
5

Недавно я столкнулся с трудностями при попытке добавить AddRange (IEnumerable) в список. Вероятно, это классический вопрос, но я пока не понимаю его.C# Невозможно преобразовать из IEnumerable <Base> в IEnumerable <Derived>

Я понимаю, что методы, ожидающие параметра List, не удовлетворяются с List, потому что они могут попытаться добавить базу в список, что, очевидно, невозможно.

Но если я получу это правильно, поскольку IEnumerables сами не могут быть изменены, в этом случае он должен работать.

код я думал выглядит следующим образом:

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 
    } 
} 

Я попытался передать намек на компилятор в методе AddRange2, но это только что переехал в проблеме вокруг.

Является ли мой образ мышления ошибочным? Это ограничение языка или его дизайн?

IIRC, поддержка такого рода операций была добавлена ​​в Java 1.5, так что, возможно, в какой-то момент она будет добавлена ​​и в будущем ...?

+0

Дубликат различных других вопросов, в том числе совсем недавно http://stackoverflow.com/questions/632399 –

+0

Действительно, извините. Мой google-jutsu все еще нуждается в улучшении. – mafu

+0

Нет проблем. Было бы неплохо иметь FAQ по каждому тегу, IMO ... –

ответ

18

Это ковариация и будет исправлена ​​в C# 4.0/.NET 4.0. На данный момент общий вариант - лучший ответ (для IEnumerable<T> - not IList<T> etc).

Но в рамках общего метода вы должны думать в терминах T. Вы также можете использовать Cast<T> или OfType<T> с LINQ, чтобы достичь чего-то подобного.

+1

Спасибо за новый термин, ковариация. Не знал о существовании. – Sung

0

Существует обходной путь с помощью метода расширения:

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

но "< Person, Employee>" это немного неудобно: /.

+0

Cast () будет проще ... –

2

В C# 3.0 вы можете использовать метод расширения «Cast». Если вы импортируете System.Linq и затем используете этот код:

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

Тогда он должен сработать для вас.

0

Литой вариант, конечно, может привести к исключению класса исключений. Человек, который отправил переписную работу, сказал, что это было неловко. я придумал решение, которое является только половина неловко, не знаю, если я буду использовать его:

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

Использование:

IEnumerable млекопитающих = UPTO < Млекопитающее > .С (питомник. Собаки)