Em C #, por que um objeto List <string> não pode ser armazenado em uma variável List <object>

85

Parece que um objeto List não pode ser armazenado em uma variável List em C # e nem pode ser convertido explicitamente dessa forma.

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

resulta em não é possível converter implicitamente o tipo System.Collections.Generic.List<string>paraSystem.Collections.Generic.List<object>

E depois...

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

resulta em Não é possível converter o tipo System.Collections.Generic.List<string>paraSystem.Collections.Generic.List<object>

Claro, você pode fazer isso retirando tudo da lista de strings e colocando de volta um de cada vez, mas é uma solução bastante complicada.

Matt Sheppard
fonte
5
Isso vai mudar com o C # 4.0, então você pode querer pesquisar a covariância e a contravariância. Isso permitirá essas coisas de maneira segura.
John Leidegren,
Mais ou menos duplicado: stackoverflow.com/questions/317335/…
Joel em Gö
7
John> C # 4 não permitirá isso. Pense em ol.Add (new object ());
Guillaume,
É respondido por Jon Skeet aqui: stackoverflow.com/a/2033921/1070906
JCH2k

Respostas:

39

Pense desta forma, se você fosse fazer tal conversão e, em seguida, adicionar um objeto do tipo Foo à lista, a lista de strings não seria mais consistente. Se você iterar a primeira referência, obterá uma exceção de conversão de classe porque, uma vez que você acerta a instância Foo, o Foo não pode ser convertido em string!

Como observação lateral, acho que seria mais significativo se você pudesse ou não fazer o cast reverso:

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

Eu não uso o C # há algum tempo, então não sei se isso é legal, mas esse tipo de elenco é na verdade (potencialmente) útil. Neste caso, você está indo de uma classe (objeto) mais geral para uma classe mais específica (string) que se estende da classe geral. Dessa forma, se você adicionar à lista de strings, não estará violando a lista de objetos.

Alguém sabe ou pode testar se tal elenco é legal em C #?

Mike Stone
fonte
4
Acho que seu exemplo sofre do mesmo problema. O que acontece se ol tiver algo que não seja uma string? O problema é que alguns métodos em List funcionariam bem, como adicionar / inserir. Mas a iteração pode ser um problema real.
Chris Ammerman
3
Eric Lippert tem uma ótima série de postagens de blog sobre este tópico: por que pode funcionar para adicionar restrições de covariância e contravariância a métodos genéricos, mas pode nunca funcionar da maneira que gostaríamos no nível de classe. is.gd/3kQc
Chris Ammerman
@ChrisAmmerman: Se olhouvesse algo que não fosse uma string, suponho que esperaria que o elenco falhasse no tempo de execução. Mas onde você realmente teria problemas é se o elenco tivesse sucesso, e então algo fosse adicionado a olisso não é uma corda. Como faz slreferência ao mesmo objeto, agora seu List<string>conteria uma não string. Este Addé o problema, que eu acho que justifica porque este código não compila, mas ele irá compilar se você mudar List<object> olpara IEnumerable<object> ol, que não tem um Add. (Eu verifiquei isso em C # 4.)
Tim Goodman
Com essa alteração, ele compila, mas lança um InvalidCastExceptionporque o tipo de tempo de execução de olainda está List<object>.
Tim Goodman
36

Se você estiver usando o .NET 3.5, dê uma olhada no método Enumerable.Cast. É um método de extensão para que você possa chamá-lo diretamente na Lista.

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

Não é exatamente o que você pediu, mas deve servir.

Edit: Conforme observado por Zooba, você pode chamar ol.ToList () para obter uma lista

Raio
fonte
14

Você não pode converter entre tipos genéricos com parâmetros de tipo diferentes. Tipos genéricos especializados não fazem parte da mesma árvore de herança e, portanto, são tipos não relacionados.

Para fazer isso pré-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
}

Usando o 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
fonte
11

A razão é que uma classe genérica como List<>é, para a maioria dos propósitos, tratada externamente como uma classe normal. por exemplo, quando você dizList<string>() o compilador diz ListString()(que contém strings). [Pessoal técnico: esta é uma versão inglesa extremamente simples do que está acontecendo]

Conseqüentemente, obviamente o compilador não pode ser inteligente o suficiente para converter um ListString em um ListObject lançando os itens de sua coleção interna.

É por isso que existem métodos de extensão para IEnumerable como Convert () que permitem que você forneça facilmente a conversão para os itens armazenados dentro de uma coleção, o que poderia ser tão simples quanto converter um para outro.

Rex M
fonte
6

Isso tem muito a ver com a covariância, por exemplo, tipos genéricos são considerados parâmetros e, se os parâmetros não forem resolvidos adequadamente para um tipo mais específico, a operação falhará. A implicação disso é que você realmente não pode lançar para um tipo mais geral, como objeto. E conforme afirmado por Rex, o objeto List não converterá cada objeto para você.

Você pode tentar o código ff em vez disso:

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

ou:

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

ol irá (teoricamente) copiar todo o conteúdo do sl sem problemas.

Jon Limjap
fonte
5

Sim, você pode, no .NET 3.5:

List<string> sl = new List<string>();
List<object> ol = sl.Cast<object>().ToList();
Tamas Czinege
fonte
1

Isso é realmente para que você não tente colocar qualquer "objeto" estranho em sua "velha" variante de lista (como List<object>parece permitir) - porque seu código travaria então (porque a lista realmente é List<string>e só aceitará objetos do tipo String ) É por isso que você não pode converter sua variável para uma especificação mais geral.

Em Java é o contrário, você não tem genéricos e, em vez disso, tudo é Lista de objetos em tempo de execução, e você realmente pode colocar qualquer objeto estranho em sua Lista supostamente digitada com rigidez. Pesquise por "Reified generics" para ver uma discussão mais ampla sobre o problema de java ...

Valters Vingolds
fonte
1

Essa covariância em genéricos não é suportada, mas você pode realmente fazer isso com matrizes:

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

C # executa verificações de tempo de execução para evitar que você coloque, digamos, um intem a.

Constantin
fonte
0

Aqui está outra solução pré-.NET 3.5 para qualquer IList cujo conteúdo pode ser lançado implicitamente.

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;
}

(Com base no exemplo de Zooba)

Alvin Ashcraft
fonte
0

Eu tenho um:

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

E eu iria preenchê-lo com dados coletados em um. List<object> O que finalmente funcionou para mim foi este:

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

.Castpara o tipo que você deseja obter IEnumerabledesse tipo e, em seguida, digite o IEnemuerablepara o tipo List<>desejado.

Menno
fonte
0

Mm, graças aos comentários anteriores, encontrei duas maneiras de descobrir isso. O primeiro é obter a lista de strings de elementos e, em seguida, lançá-la na lista de objetos IEnumerable:

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

E o segundo é evitar o tipo de objeto IEnumerable, apenas convertendo a string para o tipo de objeto e, em seguida, usando a função "toList ()" na mesma frase:

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

Gosto mais da segunda forma. Eu espero que isso ajude.

Cristinadeveloper
fonte
0
List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Kozen
fonte