Почему в C # нельзя сохранить объект List <string> в переменной List <object>

85

Кажется, что объект List не может быть сохранен в переменной List в C # и даже не может быть явно приведен таким образом.

List<string> sl = new List<string>();
List<object> ol;
ol = sl;

приводит к невозможности неявно преобразовать тип System.Collections.Generic.List<string>вSystem.Collections.Generic.List<object>

А потом...

List<string> sl = new List<string>();
List<object> ol;
ol = (List<object>)sl;

приводит к невозможности преобразования типа System.Collections.Generic.List<string>вSystem.Collections.Generic.List<object>

Конечно, вы можете сделать это, вытащив все из списка строк и вставив обратно по одной, но это довольно запутанное решение.

Мэтт Шеппард
источник
5
Это изменится в C # 4.0, поэтому вы можете захотеть найти ковариацию и контравариантность. Это позволит делать такие вещи безопасным способом.
Джон Лейдегрен, 04
Более или менее дублирующий: stackoverflow.com/questions/317335/…
Joel in Gö
7
Джон> C # 4 не позволит этого. Подумайте о ol.Add (новый объект ());
Гийом,
Здесь отвечает Джон Скит: stackoverflow.com/a/2033921/1070906
JCH2k

Ответы:

39

Подумайте об этом так: если бы вы сделали такое приведение, а затем добавили в список объект типа Foo, список строк больше не согласован. Если бы вы перебрали первую ссылку, вы бы получили исключение приведения класса, потому что после того, как вы нажмете на экземпляр Foo, Foo не может быть преобразовано в строку!

В качестве побочного примечания, я думаю, было бы более важно, сможете ли вы выполнить обратное приведение:

List<object> ol = new List<object>();
List<string> sl;
sl = (List<string>)ol;

Я давно не использовал C #, поэтому не знаю, законно ли это, но такой вид приведений на самом деле (потенциально) полезен. В этом случае вы переходите от более общего класса (объекта) к более конкретному классу (строке), который расширяется от общего. Таким образом, если вы добавляете в список строк, вы не нарушаете список объектов.

Кто-нибудь знает или может проверить, допустимо ли такое приведение в C #?

Майк Стоун
источник
4
Я думаю, что ваш пример страдает той же проблемой. Что произойдет, если в ol есть что-то, кроме строки? Проблема в том, что некоторые методы в List будут работать нормально, например, добавление / вставка. Но повторение может быть настоящей проблемой.
Крис Аммерман,
3
У Эрика Липперта есть отличная серия сообщений в блоге на эту тему: почему может сработать добавление ограничений ковариации и контравариантности к универсальным методам, но может никогда не сработать так, как мы хотели бы на уровне класса. is.gd/3kQc
Крис Аммерман,
@ChrisAmmerman: Если olбы в нем было что-то, кроме строки, я бы ожидал, что приведение не удастся во время выполнения. Но где вы действительно столкнетесь с проблемами, так это если приведение выполнено успешно, а затем к нему было добавлено olчто-то, а не строка. Поскольку slссылается на один и тот же объект, теперь ваш List<string>будет содержать не строку. Это Addпроблема, которая, я думаю, объясняет, почему этот код не компилируется, но он будет компилироваться, если вы измените его List<object> olна IEnumerable<object> ol, который не имеет файла Add. (Я проверил это на C # 4)
Тим Гудман
С этим изменением он компилируется, но генерирует, InvalidCastExceptionпотому что тип среды выполнения olостается прежним List<object>.
Тим Гудман,
36

Если вы используете .NET 3.5, обратите внимание на метод Enumerable.Cast. Это метод расширения, поэтому вы можете вызывать его прямо в списке.

List<string> sl = new List<string>();
IEnumerable<object> ol;
ol = sl.Cast<object>();

Это не совсем то, о чем вы просили, но должно помочь.

Изменить: как отметил Zooba, вы можете затем вызвать ol.ToList (), чтобы получить список

Луч
источник
14

Вы не можете выполнять приведение между универсальными типами с разными параметрами типа. Специализированные универсальные типы не являются частью одного и того же дерева наследования, как и несвязанные типы.

Для этого до NET 3.5:

List<string> sl = new List<string>();
// Add strings to sl

List<object> ol = new List<object>();

foreach(string s in sl)
{
    ol.Add((object)s);  // The cast is performed implicitly even if omitted
}

Используя Linq:

var sl = new List<string>();
// Add strings to sl

var ol = new List<object>(sl.Cast<object>());

// OR
var ol = sl.Cast<object>().ToList();

// OR (note that the cast to object here is required)
var ol = sl.Select(s => (object)s).ToList();
Zooba
источник
11

Причина в том, что универсальный класс вроде List<>для большинства целей обрабатывается извне как обычный класс. например, когда вы говорите, List<string>()что компилятор говорит ListString()(который содержит строки). [Технический специалист: это чрезвычайно простая английская версия того, что происходит]

Следовательно, очевидно, что компилятор не может быть достаточно умен, чтобы преобразовать ListString в ListObject путем преобразования элементов его внутренней коллекции.

Вот почему существуют методы расширения для IEnumerable, такие как Convert (), которые позволяют легко выполнять преобразование для элементов, хранящихся внутри коллекции, что может быть таким же простым, как преобразование из одного в другой.

Рекс М
источник
6

Это во многом связано с ковариацией, например, общие типы рассматриваются как параметры, и если параметры не разрешаются должным образом в более конкретный тип, операция завершается ошибкой. Следствием этого является то, что вы действительно не можете привести к более общему типу, например объекту. И, как заявил Рекс, объект List не будет преобразовывать каждый объект за вас.

Вместо этого вы можете попробовать код ff:

List<string> sl = new List<string>();
//populate sl
List<object> ol = new List<object>(sl);

или:

List<object> ol = new List<object>();
ol.AddRange(sl);

ol (теоретически) скопирует все содержимое sl без проблем.

Джон Лимджап
источник
5

Да, из .NET 3.5 можно:

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();
Тамаш Чинеге
источник
1

Это на самом деле для того, чтобы вы не пытались поместить какой-либо нечетный «объект» в свой вариант списка «ol» ( List<object>что, казалось бы, допускает) - потому что тогда ваш код выйдет из строя (поскольку список действительно есть List<string>и будет принимать только объекты типа String ). Вот почему вы не можете привести переменную к более общей спецификации.

На Java все наоборот, у вас нет универсальных шаблонов, и вместо этого все является списком объектов во время выполнения, и вы действительно можете поместить любой странный объект в свой предположительно строго типизированный список. Найдите "Reified generics", чтобы увидеть более подробное обсуждение проблемы Java ...

Валтерс Винголдс
источник
1

Такая ковариация для дженериков не поддерживается, но вы можете сделать это с помощью массивов:

object[] a = new string[] {"spam", "eggs"};

C # выполняет проверки во время выполнения, чтобы вы не поместили, скажем, intв a.

Константин
источник
0

Вот еще одно решение до .NET 3.5 для любого списка IList, содержимое которого может быть неявно приведено.

public IList<B> ConvertIList<D, B>(IList<D> list) where D : B
{
    List<B> newList = new List<B>();

    foreach (D item in list)
    {
        newList.Add(item);
    }

    return newList;
}

(На примере Zooba)

Элвин Эшкрафт
источник
0

У меня есть:

private List<Leerling> Leerlingen = new List<Leerling>();

И я собирался заполнить его данными, собранными в. List<object> Что, наконец, сработало для меня:

Leerlingen = (List<Leerling>)_DeserialiseerLeerlingen._TeSerialiserenObjecten.Cast<Leerling>();

.Castэто типа вы хотите получить IEnumerableот этого типа, то напечатанный IEnemuerableк List<>вы хотите.

Menno
источник
0

Мм, благодаря предыдущим комментариям я нашел два способа выяснить это. Первый - это получение строкового списка элементов и преобразование его в список объектов IEnumerable:

IEnumerable<object> ob;
List<string> st = new List<string>();
ob = st.Cast<object>();

И второй - избегает объектного типа IEnumerable, просто преобразуя строку в объектный тип, а затем используя функцию «toList ()» в том же предложении:

List<string> st = new List<string>();
List<object> ob = st.Cast<object>().ToList();

Мне больше нравится второй способ. Надеюсь, это поможет.

Кристина разработчик
источник
0
List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Козен
источник