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

Кажется, что объект 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>

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


person Matt Sheppard    schedule 09.08.2008    source источник
comment
Это изменится в C # 4.0, так что вам может потребоваться поиск ковариации и контравариантности. Это позволит делать такие вещи безопасным способом.   -  person John Leidegren    schedule 04.03.2009
comment
Более или менее повторяется: stackoverflow.com/questions/317335/   -  person Joel in Gö    schedule 20.05.2009
comment
Джон ›C # 4 не допускает этого. Подумайте о ol.Add (новый объект ());   -  person Guillaume    schedule 22.03.2010
comment
Здесь отвечает Джон Скит: stackoverflow.com/a/2033921/1070906   -  person JCH2k    schedule 22.07.2015


Ответы (13)


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

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

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

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

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

person Mike Stone    schedule 09.08.2008
comment
Я думаю, что ваш пример страдает той же проблемой. Что произойдет, если в ol есть что-то, кроме строки? Проблема в том, что некоторые методы в List будут работать нормально, например, добавление / вставка. Но повторение может стать настоящей проблемой. - person Chris Ammerman; 30.09.2008
comment
У Эрика Липперта есть отличная серия сообщений в блоге на эту тему: почему может сработать добавление ограничений ковариации и контравариантности к универсальным методам, но может никогда не сработать так, как хотелось бы, на уровне класса. is.gd/3kQc - person Chris Ammerman; 30.09.2008
comment
@ChrisAmmerman: Если бы в ol было что-то, кроме строки, я полагаю, я ожидал бы сбоя приведения во время выполнения. Но где вы действительно столкнетесь с проблемами, так это если приведение выполнено успешно, и затем что-то было добавлено к ol, что не является строкой. Поскольку sl ссылается на тот же объект, теперь ваш List<string> будет содержать не строку. Add - это проблема, которая, я думаю, объясняет, почему этот код не компилируется, но он будет компилироваться, если вы измените List<object> ol на IEnumerable<object> ol, который не имеет Add. (Я проверил это на C # 4). - person Tim Goodman; 02.08.2013
comment
С этим изменением он компилируется, но выдает InvalidCastException, потому что тип среды выполнения ol по-прежнему List<object>. - person Tim Goodman; 02.08.2013

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

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

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

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

person Ray    schedule 09.08.2008

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

Для этого до 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();
person Zooba    schedule 10.08.2008

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

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

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

person Rex M    schedule 09.08.2008

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

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

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

or:

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

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

person Jon Limjap    schedule 09.08.2008

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

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();
person Tamas Czinege    schedule 18.10.2008

Майк - я считаю, что контравариантность также недопустима в C #.

См. Вариант параметра универсального типа в среде CLR, больше информации.

person Matt Sheppard    schedule 09.08.2008

Я думаю, что это (контравариантность) действительно будет поддерживаться в C # 4.0. http://blogs.msdn.com/charlie/archive/2008/10/27/linq-farm-covariance-and-contravariance-in-visual-studio-2010.aspx

person MattValerio    schedule 07.11.2008

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

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

person Valters Vingolds    schedule 10.08.2008

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

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

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

person Constantin    schedule 09.10.2008

Вот еще одно решение до .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;
}

(На примере Зубы)

person Alvin Ashcraft    schedule 08.06.2009

У меня есть:

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

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

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

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

person Menno    schedule 15.10.2009

Мм, благодаря предыдущим комментариям я нашел два способа выяснить это. Первый - это получение строкового списка элементов и преобразование его в список объектов 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();

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

person Cristinadeveloper    schedule 17.03.2016