Невозможно преобразовать из списка <DerivedClass> в список <BaseClass>
Я пытаюсь передать список DerivedClass
функции, которая принимает список BaseClass
, но я получаю сообщение об ошибке:
cannot convert from
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>'
to
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>'
Теперь я мог бы наложить List<DerivedClass>
на List<BaseClass>
, но я не чувствую себя комфортно, если не понимаю, почему компилятор этого не допускает.
Объяснения, которые я нашел, просто сказали, что это как-то нарушает безопасность типа, но я этого не вижу. Может ли кто-нибудь помочь мне?
В чем заключается риск компилятора, позволяющего преобразовать с List<DerivedClass>
в List<BaseClass>
?
Здесь мой SSCCE:
class Program
{
public static void Main()
{
BaseClass bc = new DerivedClass(); // works fine
List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error
doSomething(new List<DerivedClass>()); // this line has an error
}
public void doSomething(List<BaseClass> bc)
{
// do something with bc
}
}
class BaseClass
{
}
class DerivedClass : BaseClass
{
}
Ответы
Ответ 1
Это потому, что List<T>
есть in-variant
, а не co-variant
, поэтому вы должны перейти на IEnumerable<T>
, который поддерживает co-variant
, он должен работать:
IEnumerable<BaseClass> bcl = new List<DerivedClass>();
public void doSomething(IEnumerable<BaseClass> bc)
{
// do something with bc
}
Информация о совместном варианте в родовом формате
Ответ 2
Объяснения, которые я нашел, просто сказали, что это как-то нарушает безопасность типа, но я этого не вижу. В чем заключается риск компилятора, позволяющего преобразовать с List<DerivedClass>
в List<BaseClass>
?
Этот вопрос задают почти каждый день.
A List<Mammal>
не может быть преобразован в List<Animal>
, потому что вы можете помещать ящерицу в список животных. A List<Mammal
> не может быть преобразован в List<Giraffe>
, потому что в списке может быть тигр.
Следовательно, List<T>
должно быть инвариантным в T.
Однако List<Mammal>
можно преобразовать в IEnumerable<Animal>
(начиная с С# 4.0), потому что на IEnumerable<Animal>
нет метода, который добавляет ящерицу. IEnumerable<T>
ковариантно в T.
Ответ 3
Поведение, которое вы описываете, называется covariance – если A
B
, то List<A>
есть List<B>
.
Однако для изменяемых типов, таких как List<T>
, это принципиально небезопасно.
Если бы это было возможно, метод мог бы добавить new OtherDerivedClass()
в список, который может содержать только DerivedClass
.
Ковариация безопасна для неизменяемых типов, хотя .Net поддерживает ее только в интерфейсах и делегатах.
Если вы измените параметр List<T>
на IEnumerable<T>
, это будет работать
Ответ 4
Когда у вас есть класс, полученный из базового класса, любые контейнеры этих классов не производятся автоматически. Поэтому вы не можете просто нарисовать List<Derived>
до List<Base>
.
Используйте .Cast<T>()
для создания нового списка, в который каждый объект возвращается в базовый класс:
List<MyDerived> list1 = new List<MyDerived>();
List<MyBase> list2 = list1.Cast<MyBase>().ToList();
Обратите внимание, что это новый список, а не литая версия исходного списка, поэтому операции над этим новым списком не будут отображаться в исходном списке. Однако операции над содержащимися объектами будут отображаться.
Ответ 5
Если вы могли бы написать
List<BaseClass> bcl = new List<DerivedClass>();
вы можете позвонить
var instance = new AnotherClassInheritingFromBaseClass();
bc1.Add(instance);
Добавление экземпляра, который не является DerivedClass в список.
Ответ 6
Решением, которое я использовал, было создание класса расширения:
public static class myExtensionClass
{
public void doSomething<T>(List<BaseClass> bc) where T: BaseClass
{
// do something with bc
}
}
Он использует общий, но когда вы его вызываете, вам не нужно выделять класс, поскольку вы уже "сказали" компилятору, что тот же тип расширенного класса.
Вы бы назвали это следующим образом:
List<DerivedClass> lst = new List<DerivedClass>();
lst.doSomething();