C # Не удается преобразовать из IEnumerable <Base>в IEnumerable <Derived> - PullRequest
5 голосов
/ 11 марта 2009

Я недавно столкнулся с проблемой при попытке добавить AddRange (IEnumerable) в список. Вероятно, классическая проблема, но я пока не понимаю.

Я понимаю, что методы, ожидающие параметр 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, поэтому, возможно, она будет добавлена ​​в C # в будущем, тоже ...?

Ответы [ 4 ]

18 голосов
/ 11 марта 2009

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

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

2 голосов
/ 18 марта 2009

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

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

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

0 голосов
/ 26 сентября 2009

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

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

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

IE Многочисленные млекопитающие = UpTo . From (kennel.Dogs)

0 голосов
/ 11 марта 2009

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

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

но "" немного неловко: /.

...