En C #, pourquoi un objet List <string> ne peut-il pas être stocké dans une variable List <object>

85

Il semble qu'un objet List ne peut pas être stocké dans une variable List en C #, et ne peut même pas être explicitement converti de cette façon.

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

résultats dans Impossible de convertir implicitement le type System.Collections.Generic.List<string>enSystem.Collections.Generic.List<object>

Puis...

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

résultats dans Impossible de convertir le type System.Collections.Generic.List<string>enSystem.Collections.Generic.List<object>

Bien sûr, vous pouvez le faire en retirant tout de la liste de chaînes et en le remettant un à la fois, mais c'est une solution plutôt compliquée.

Matt Sheppard
la source
5
Cela va changer avec C # 4.0, vous voudrez peut-être rechercher la covariance et la contravariance. Cela permettra de telles choses d'une manière sûre.
John Leidegren
Plus ou moins en double: stackoverflow.com/questions/317335/…
Joel in Gö
7
John> C # 4 ne permettra pas cela. Pensez à ol.Add (new object ());
Guillaume
Il est répondu par Jon Skeet ici: stackoverflow.com/a/2033921/1070906
JCH2k

Réponses:

39

Pensez-y de cette façon, si vous deviez faire un tel cast, puis ajouter un objet de type Foo à la liste, la liste des chaînes n'est plus cohérente. Si vous répétez la première référence, vous obtiendrez une exception de conversion de classe car une fois que vous avez atteint l'instance Foo, le Foo ne peut pas être converti en chaîne!

En passant, je pense qu'il serait plus important de savoir si vous pouvez ou non faire la fonte inversée:

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

Je n'ai pas utilisé C # depuis un moment, donc je ne sais pas si c'est légal, mais ce genre de distribution est en fait (potentiellement) utile. Dans ce cas, vous passez d'une classe plus générale (objet) à une classe plus spécifique (chaîne) qui s'étend de la classe générale. De cette façon, si vous ajoutez à la liste des chaînes, vous ne violez pas la liste des objets.

Quelqu'un sait-il ou peut-il tester si une telle distribution est légale en C #?

Mike Stone
la source
4
Je pense que votre exemple souffre du même problème. Que se passe-t-il si ol contient quelque chose qui n'est pas une chaîne? Le problème est que certaines méthodes de List fonctionneraient correctement, telles que l'ajout / l'insertion. Mais l'itération pourrait être un réel problème.
Chris Ammerman
3
Eric Lippert a une grande série d'articles de blog sur ce sujet: pourquoi cela pourrait fonctionner pour ajouter des contraintes de covariance et de contravariance aux méthodes génériques, mais pourrait ne jamais fonctionner comme nous le souhaiterions au niveau de la classe. is.gd/3kQc
Chris Ammerman
@ChrisAmmerman: S'il y olavait quelque chose dedans qui n'est pas une chaîne, je suppose que je m'attendrais à ce que la distribution échoue à l'exécution. Mais là où vous auriez vraiment des problèmes, c'est si le casting réussissait, puis quelque chose a été ajouté à ce olqui n'est pas une chaîne. Parce que fait slréférence au même objet, maintenant votre List<string>contiendrait une non-chaîne. C'est Addle problème, qui, je suppose, justifie pourquoi ce code ne se compilera pas, mais il se compilera si vous passez List<object> olà IEnumerable<object> ol, qui n'a pas de fichier Add. (J'ai vérifié cela en C # 4.)
Tim Goodman
Avec ce changement, il compile mais lève un InvalidCastExceptioncar le type d'exécution de olest toujours List<object>.
Tim Goodman
36

Si vous utilisez .NET 3.5, jetez un œil à la méthode Enumerable.Cast. C'est une méthode d'extension afin que vous puissiez l'appeler directement sur la liste.

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

Ce n'est pas exactement ce que vous avez demandé, mais cela devrait faire l'affaire.

Edit: Comme indiqué par Zooba, vous pouvez ensuite appeler ol.ToList () pour obtenir une liste

Rayon
la source
14

Vous ne pouvez pas effectuer de conversion entre des types génériques avec des paramètres de type différents. Les types génériques spécialisés ne font pas partie de la même arborescence d'héritage, de même que les types non liés.

Pour ce faire 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
}

Utilisation de 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
la source
11

La raison en est qu'une classe générique comme List<>est, dans la plupart des cas, traitée en externe comme une classe normale. par exemple, quand vous dites que List<string>()le compilateur dit ListString()(qui contient des chaînes). [Techniciens: ceci est une version extrêmement simple en anglais de ce qui se passe]

Par conséquent, le compilateur ne peut évidemment pas être assez intelligent pour convertir un ListString en ListObject en convertissant les éléments de sa collection interne.

C'est pourquoi il existe des méthodes d'extension pour IEnumerable comme Convert () qui vous permettent de fournir facilement une conversion pour les éléments stockés dans une collection, ce qui pourrait être aussi simple que de passer de l'un à l'autre.

Rex M
la source
6

Cela a beaucoup à voir avec la covariance, par exemple, les types génériques sont considérés comme des paramètres, et si les paramètres ne se résolvent pas correctement en un type plus spécifique, l'opération échoue. L'implication d'un tel est que vous ne pouvez vraiment pas convertir en un type plus général comme object. Et comme indiqué par Rex, l'objet List ne convertira pas chaque objet pour vous.

Vous voudrez peut-être essayer le code FF à la place:

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 copiera (théoriquement) tout le contenu de sl sans problème.

Jon Limjap
la source
5

Oui, vous pouvez, à partir de .NET 3.5:

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

C'est en fait pour que vous n'essayiez pas de mettre un "objet" étrange dans votre variante de liste "ol" (comme List<object>cela semble le permettre) - parce que votre code planterait alors (parce que la liste est vraiment List<string>et n'acceptera que des objets de type String ). C'est pourquoi vous ne pouvez pas convertir votre variable en une spécification plus générale.

Sur Java, c'est l'inverse, vous n'avez pas de génériques, et à la place, tout est Liste d'objets au moment de l'exécution, et vous pouvez vraiment insérer n'importe quel objet étrange dans votre liste supposée strictement typée. Recherchez "Reified generics" pour voir une discussion plus large sur le problème de Java ...

Valters Vingolds
la source
1

Une telle covariance sur les génériques n'est pas prise en charge, mais vous pouvez le faire avec des tableaux:

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

C # effectue des vérifications à l'exécution pour vous empêcher de mettre, disons, un intdans a.

Constantin
la source
0

Voici une autre solution pré-.NET 3.5 pour tout IList dont le contenu peut être casté implicitement.

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

(Basé sur l'exemple de Zooba)

Alvin Ashcraft
la source
0

J'ai un:

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

Et j'allais le remplir avec des données collectées dans un List<object> Ce qui a finalement fonctionné pour moi était celui-ci:

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

.Castvers le type que vous souhaitez obtenir à IEnumerablepartir de ce type, puis transtypez le IEnemuerabledans le type List<>souhaité.

Menno
la source
0

Mm, grâce aux commentaires précédents, j'ai trouvé deux façons de le découvrir. Le premier consiste à obtenir la liste de chaînes des éléments, puis à la convertir en liste d'objets IEnumerable:

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

Et le second évite le type d'objet IEnumerable, en convertissant simplement la chaîne en type d'objet, puis en utilisant la fonction "toList ()" dans la même phrase:

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

J'aime plus la seconde façon. J'espère que ça aide.

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