C# Невозможно преобразовать IEnumerable<Base> в IEnumerable<Derived>

StackOverflow https://stackoverflow.com/questions/634268

Вопрос

Недавно у меня возникли проблемы при попытке добавитьRange(IEnumerable) в список.Наверное, это классическая проблема, но я ее еще не совсем понял.

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

Но если я правильно понимаю, поскольку сами 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#...?

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

но «<Человек, Сотрудник>» немного неудобно :/.

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

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<Млекопитающее>.From(kennel.Dogs)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top