В 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>

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

14 ответов

Решение

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

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

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

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

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

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

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

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

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

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

Для этого предварительно 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();

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

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

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

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

Да, вы можете из.NET 3.5:

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

Майк - я считаю, что контравариантность не разрешена в C#

Посмотрите Универсальный параметр параметра дисперсия в CLR для получения дополнительной информации.

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

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

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

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

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

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

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

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

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

У меня есть:

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

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

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

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

List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Другие вопросы по тегам