Почему я не могу назначить список <Derived>списку <Base>? - PullRequest
22 голосов
/ 11 января 2011

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

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;
        }

Ответы [ 5 ]

54 голосов
/ 11 января 2011

Простейшим способом понять, почему это не разрешено, является следующий пример:

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.

Однако массивы разрешаютthis:

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 голосов
/ 11 января 2011

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

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;
    }
}
2 голосов
/ 11 января 2011

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

Вы просите компилятор трактовать 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>.

1 голос
/ 11 января 2011

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

var calls = package.getCalls().Cast<AbstractPackageCall>();
0 голосов
/ 11 января 2011

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

Подумайте, разрешено ли это приведение:

class AnotherPackageCall : AbstractPackageCall { }

// ...

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

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

...