Почему универсальной коллекции объектов не может быть назначен общий набор строк в C # [дубликат]

Имейте в виду, что независимо от сценария причина всегда одинакова в .NET:

Вы пытаетесь использовать ссылочную переменную, значение которой Nothing / null. Если для ссылочной переменной значение Nothing / null, это означает, что на самом деле оно не содержит ссылку на экземпляр любого объекта, который существует в куче.

Вы либо никогда не присваивали какую-либо переменную, никогда не создавали экземпляр значения, присвоенного переменной, или вы вручную устанавливали переменную, равную Nothing / null, или вы вызывали функцию, которая установите для этой переменной значение Nothing / null.

22
задан Jon 11 January 2011 в 00:56
поделиться

5 ответов

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

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 для параметров типа.

54
ответ дан JulianR 25 August 2018 в 21:19
поделиться

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

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
ответ дан Artur Mustafin 25 August 2018 в 21:19
поделиться

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

var calls = package.getCalls().Cast<AbstractPackageCall>();
1
ответ дан Brian Ball 25 August 2018 в 21:19
поделиться

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

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

class AnotherPackageCall : AbstractPackageCall { }

// ...

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

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

0
ответ дан cdhowie 25 August 2018 в 21:19
поделиться

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

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

2
ответ дан Jon 25 August 2018 в 21:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: