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

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

37 голосов
/ 09 августа 2008

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

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

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

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

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

36 голосов
/ 09 августа 2008

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

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

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

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

14 голосов
/ 10 августа 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();
11 голосов
/ 09 августа 2008

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

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

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

6 голосов
/ 09 августа 2008

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

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

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();
3 голосов
/ 09 августа 2008

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

См. Дисперсия параметра общего типа в CLR для получения дополнительной информации.

2 голосов
/ 07 ноября 2008

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

1 голос
/ 10 октября 2008

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

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

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

1 голос
/ 10 августа 2008

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...