En C #, ¿por qué no se puede almacenar un objeto List <string> en una variable List <object>?

85

Parece que un objeto List no se puede almacenar en una variable List en C #, y ni siquiera se puede convertir explícitamente de esa manera.

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

da como resultado No se puede convertir implícitamente el tipo System.Collections.Generic.List<string>aSystem.Collections.Generic.List<object>

Y entonces...

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

resultados en No se puede convertir el tipo System.Collections.Generic.List<string>aSystem.Collections.Generic.List<object>

Por supuesto, puede hacerlo sacando todo de la lista de cadenas y volviéndolo a colocar uno a la vez, pero es una solución bastante complicada.

Matt Sheppard
fuente
5
Esto va a cambiar con C # 4.0, por lo que es posible que desee buscar covarianza y contravarianza. Permitirá tales cosas de una manera segura.
John Leidegren
Más o menos duplicado: stackoverflow.com/questions/317335/…
Joel en Gö
7
John> C # 4 no permitirá esto. Piense en ol.Add (new object ());
Guillaume
Jon Skeet la responde aquí: stackoverflow.com/a/2033921/1070906
JCH2k

Respuestas:

39

Piénselo de esta manera, si tuviera que hacer tal conversión y luego agregar un objeto de tipo Foo a la lista, la lista de cadenas ya no es consistente. Si tuviera que iterar la primera referencia, obtendría una excepción de conversión de clase porque una vez que golpeó la instancia de Foo, ¡Foo no podría convertirse en cadena!

Como nota al margen, creo que sería más significativo si puedes o no hacer el reparto inverso:

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

No he usado C # en un tiempo, así que no sé si eso es legal, pero ese tipo de transmisión es realmente (potencialmente) útil. En este caso, se pasa de una clase (objeto) más general a una clase (cadena) más específica que se extiende desde la general. De esta manera, si agrega a la lista de cadenas, no está violando la lista de objetos.

¿Alguien sabe o puede probar si un elenco de este tipo es legal en C #?

Mike Stone
fuente
4
Creo que tu ejemplo adolece del mismo problema. ¿Qué sucede si ol tiene algo que no es una cadena? El problema es que algunos métodos en List funcionarían bien, como agregar / insertar. Pero la iteración podría ser un problema real.
Chris Ammerman
3
Eric Lippert tiene una gran serie de publicaciones en el blog sobre este tema: por qué podría funcionar agregar restricciones de covarianza y contravarianza a métodos genéricos, pero es posible que nunca funcione como nos gustaría a nivel de clase. is.gd/3kQc
Chris Ammerman
@ChrisAmmerman: Si oltuviera algo que no fuera una cadena, supongo que esperaría que el elenco fallara en tiempo de ejecución. Pero donde realmente te meterías en problemas es si el elenco tuvo éxito y luego se agregó algo olque no es una cadena. Debido a que hace slreferencia al mismo objeto, ahora tu List<string>contendría una no cadena. El Addes el problema, que supongo que justifica por qué este código no se compilará, pero se compilará si cambia List<object> ola IEnumerable<object> ol, que no tiene una extensión Add. (Verifiqué esto en C # 4.)
Tim Goodman
Con ese cambio, compila pero arroja un InvalidCastExceptionporque el tipo de tiempo de ejecución de olsigue siendo List<object>.
Tim Goodman
36

Si está utilizando .NET 3.5, eche un vistazo al método Enumerable.Cast. Es un método de extensión, por lo que puede llamarlo directamente en la Lista.

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

No es exactamente lo que pediste, pero debería funcionar.

Editar: como señaló Zooba, puede llamar a ol.ToList () para obtener una lista

Rayo
fuente
14

No puede lanzar entre tipos genéricos con diferentes parámetros de tipo. Los tipos genéricos especializados no forman parte del mismo árbol de herencia y también lo son los tipos no relacionados.

Para hacer esto antes de 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 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
fuente
11

La razón es que una clase genérica como List<>, para la mayoría de los propósitos, se trata externamente como una clase normal. por ejemplo, cuando dice que List<string>()el compilador dice ListString()(que contiene cadenas). [Gente técnica: esta es una versión en inglés extremadamente simple de lo que está sucediendo]

En consecuencia, obviamente, el compilador no puede ser lo suficientemente inteligente como para convertir un ListString en un ListObject lanzando los elementos de su colección interna.

Es por eso que existen métodos de extensión para IEnumerable como Convert () que le permiten proporcionar fácilmente la conversión de los elementos almacenados dentro de una colección, lo que podría ser tan simple como pasar de uno a otro.

Rex M
fuente
6

Esto tiene mucho que ver con la covarianza, por ejemplo, los tipos genéricos se consideran parámetros, y si los parámetros no se resuelven correctamente en un tipo más específico, la operación falla. La implicación de esto es que realmente no se puede lanzar a un tipo más general como un objeto. Y como lo indicó Rex, el objeto List no convertirá cada objeto por usted.

Es posible que desee probar el código ff en su lugar:

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

o:

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

ol copiará (teóricamente) todo el contenido de sl sin problemas.

Jon Limjap
fuente
5

Sí, puede, desde .NET 3.5:

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

Eso es en realidad para que no intente poner ningún "objeto" extraño en su variante de lista "ol" (como List<object>parece permitir), porque su código fallaría entonces (porque la lista realmente lo es List<string>y solo aceptará objetos de tipo String ). Es por eso que no puede convertir su variable a una especificación más general.

En Java es al revés, no tienes genéricos, y en su lugar todo es Lista de objetos en tiempo de ejecución, y realmente puedes incluir cualquier objeto extraño en tu Lista supuestamente estrictamente escrita. Busque "Genéricos reificados" para ver una discusión más amplia sobre el problema de Java ...

Valters Vingolds
fuente
1

Tal covarianza en genéricos no es compatible, pero en realidad puede hacer esto con matrices:

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

C # realiza comprobaciones en tiempo de ejecución para evitar que coloque, digamos, un intarchivo en a.

Constantin
fuente
0

Aquí hay otra solución anterior a.NET 3.5 para cualquier IList cuyo contenido se pueda emitir implícitamente.

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

(Basado en el ejemplo de Zooba)

Alvin Ashcraft
fuente
0

Tengo un:

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

Y lo iba a llenar con datos recopilados en un List<object> Lo que finalmente funcionó para mí fue este:

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

.Castal tipo que desea obtener IEnumerablede ese tipo, luego encasilla IEnemuerableal List<>que desea.

Menno
fuente
0

Mm, gracias a los comentarios anteriores encontré dos formas de averiguarlo. El primero es obtener la lista de cadenas de elementos y luego convertirla en la lista de objetos IEnumerable:

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

Y el segundo es evitar el tipo de objeto IEnumerable, simplemente lanzando la cadena al tipo de objeto y luego usando la función "toList ()" en la misma oración:

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

Me gusta más la segunda forma. Espero que esto ayude.

Cristina desarrolladora
fuente
0
List<string> sl = new List<string>();
List<object> ol;
ol = new List<object>(sl);
Kozen
fuente