Подтвердить что ты не робот

Почему я не могу назначить List <Derived> в List <Base>?

Я определил следующий класс:

public abstract class AbstractPackageCall
    {

     ...

    }

Я также определяю подкласс этого класса:

class PackageCall : AbstractPackageCall
    {

      ...
    }

Существует также несколько других подклассов AbstractPackageCall

Теперь я хочу сделать следующий вызов:

 List<AbstractPackageCall> calls = package.getCalls();

Но я всегда получаю это исключение:

Error   13  Cannot implicitly convert type 'System.Collections.Generic.List<Prototype_Concept_2.model.PackageCall>' to 'System.Collections.Generic.List<Prototype_Concept_2.model.AbstractPackageCall>' 

В чем проблема? Это метод Package # getCalls

 internal List<PackageCall> getCalls()
        {
            return calls;
        }
4b9b3361

Ответ 1

Самый простой способ понять, почему это недопустимо, - это следующий пример:

abstract class Fruit
{
}

class Apple : Fruit
{
}

class Banana : Fruit
{
}

// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();

// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());

Последнее утверждение разрушило бы безопасность типов .NET.

Массивы, однако, позволяют это:

Fruit[] fruits = new Apple[10]; // This is perfectly fine

Однако размещение Banana в fruits будет по-прежнему нарушать безопасность типов, поэтому .NET должен выполнять проверку типа для каждой вставки массива и выдавать исключение, если это не фактически Apple. Это потенциально (небольшой) показатель производительности, но это можно обойти, создав обертку struct вокруг любого типа, поскольку эта проверка не выполняется для типов значений (потому что они не могут наследовать ни от чего). Сначала я не понял, почему это решение было принято, но вы часто сталкиваетесь с тем, почему это может быть полезно. Наиболее распространенным является String.Format, который принимает params object[], и любой массив может быть передан в это.

В .NET 4, однако, существует безопасная ковариация/контравариантность, которая позволяет вам выполнять некоторые присвоения, подобные этим, но только в том случае, если они достаточно безопасны. Что доказуемо безопасно?

IEnumerable<Fruit> fruits = new List<Apple>();

Это работает в .NET 4, потому что IEnumerable<T> стал IEnumerable<out T>. out означает, что T может только когда-либо выходить из fruits и что на IEnumerable<out T> вообще не существует метода, который когда-либо принимает T в качестве параметра, поэтому вы никогда не сможете некорректно передать Banana в IEnumerable<Fruit>.

Контравариантность - это то же самое, но я всегда забываю точные подробности об этом. Неудивительно, что для этого теперь есть ключевое слово in для параметров типа.

Ответ 2

Почему вы пытаетесь не работать

Вы просите компилятор обрабатывать List<PackageCall> как List<AbstractPackageCall>, а это не так. Другое дело, что каждый из этих экземпляров PackageCall на самом деле является AbstractPackageCall.

Что будет работать вместо

var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();

Почему то, что вы пытаетесь, никогда не будет разрешено работать

Несмотря на то, что каждый элемент внутри возвращаемого значения package.getCalls() происходит от AbstractPackageCall, мы не можем рассматривать весь список как List<AbstractPackageCall>. Здесь, что бы произошло, если бы мы могли:

var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> apcs = calls; // ILLEGAL, but assume we could do it

apcs.Add(SomeOtherConcretePackageCall()); // BOOM!

Если бы мы могли это сделать, мы могли бы добавить SomeOtherConcretePackageCall в List<PackageCall>.

Ответ 3

Теперь, если вы хотите сделать следующий вызов:

List<PackageCall> calls = package.getCalls();

// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());

Я называю это обобщение.

Кроме того, вы можете использовать это более конкретное:

List<PackageCall> calls = package.getCalls();

// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select(); 
calls.Add(new AnotherPackageCall());

Внедрите это с помощью метода расширения:

public static class AbstractPackageCallHelper
{
    public static List<U> Select(this List<T> source)
        where T : AbstractPackageCall
        where U : T
    {
        List<U> target = new List<U>();
        foreach(var element in source)
        {
            if (element is U)
            {
                 target.Add((U)element);
            }
        }
        return target;
    }
}

Ответ 4

Потому что List<PackageCall> не наследуется от List<AbstractPackageCall>. Вы можете использовать метод расширения Cast<>(), например:

var calls = package.getCalls().Cast<AbstractPackageCall>();

Ответ 5

Вы не можете выполнить это преобразование, потому что List<AbstractPackageCall> может содержать все, что происходит от AbstractPackageCall, а List<PackageCall> не может - оно может содержать только вещи, которые происходят из PackageCall.

Рассмотрим, разрешено ли это приведение:

class AnotherPackageCall : AbstractPackageCall { }

// ...

List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());

Вы только что добавили AnotherPackageCall в List<PackageCall> - но AnotherPackageCall не получается из PackageCall! Вот почему это преобразование не разрешено.