Невозможно преобразовать из списка <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();