C# Невозможно преобразовать из IEnumerable‹Base› в IEnumerable‹Derived›

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

Я понимаю, что методы, ожидающие параметр 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 # ...?


person mafu    schedule 11.03.2009    source источник


Ответы (4)


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

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

person Marc Gravell    schedule 11.03.2009
comment
Спасибо за новый термин, ковариация. Не знал о существовании. - person dance2die; 11.03.2009

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

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

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

person Scott Wisniewski    schedule 18.03.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>() );

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

person TcKs    schedule 11.03.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);
    }
}

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

IEnumerable млекопитающие = UpTo‹Mammal›.From(kennel.Dogs)

person Community    schedule 25.09.2009