C# Невозможно преобразовать IEnumerable<Base> в IEnumerable<Derived>
-
10-07-2019 - |
Вопрос
Недавно у меня возникли проблемы при попытке добавить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)