Syntaxe plus courte pour la conversion d'une liste <X> en une liste <Y>?

237

Je sais qu'il est possible de caster une liste d'éléments d'un type à un autre (étant donné que votre objet a une méthode d'opérateur explicite statique publique pour faire le casting) un à la fois comme suit:

List<Y> ListOfY = new List<Y>();

foreach(X x in ListOfX)
    ListOfY.Add((Y)x);

Mais n'est-il pas possible de lancer la liste entière en une seule fois? Par exemple,

ListOfY = (List<Y>)ListOfX;
Jimbo
la source
@Oded: J'ai juste essayé de clarifier les choses. Ne vous inquiétez pas, vous ne l'êtes pas, je comprends :)
BoltClock
1
En supposant que X dérive de Y et Z dérive de Y, pensez à ce qui se passerait si vous ajoutiez Z à votre liste <Y> qui est vraiment une liste <X>.
Richard Friend

Réponses:

497

Si vous Xpouvez vraiment vous lancer, Yvous devriez pouvoir utiliser

List<Y> listOfY = listOfX.Cast<Y>().ToList();

Quelques choses à savoir (H / T aux commentateurs!)

Jamiec
la source
12
Ayez un autre badge en or. C'était assez utile.
ouflak
6
Doit inclure la ligne suivante pour que le compilateur reconnaisse ces méthodes d'extension: using System.Linq;
hypehuman
8
Sachez également que bien que cela transforme chaque élément de la liste, la liste elle-même n'est pas castée; une nouvelle liste est plutôt créée avec le type souhaité.
hypehuman
4
Sachez également que la Cast<T>méthode ne prend pas en charge les opérateurs de conversion personnalisés. Pourquoi le Linq Cast Helper ne fonctionne-t-il pas avec l'opérateur de transfert implicite ?
clD
Cela ne fonctionne pas pour un objet qui a une méthode d'opérateur explicite (framework 4.0)
Adrian
100

La conversion directe var ListOfY = (List<Y>)ListOfXn'est pas possible car elle nécessiterait une co / contravariance du List<T>type, et cela ne peut tout simplement pas être garanti dans tous les cas. Veuillez lire la suite pour voir les solutions à ce problème de casting.

Bien qu'il semble normal de pouvoir écrire du code comme ceci:

List<Animal> animals = (List<Animal>) mammalList;

parce que nous pouvons garantir que chaque mammifère sera un animal, c'est évidemment une erreur:

List<Mammal> mammals = (List<Mammal>) animalList;

car tous les animaux ne sont pas des mammifères.

Cependant, en utilisant C # 3 et supérieur, vous pouvez utiliser

IEnumerable<Animal> animals = mammalList.Cast<Animal>();

cela facilite un peu le casting. Ceci est syntaxiquement équivalent à votre code d'ajout un par un, car il utilise une conversion explicite pour convertir chaque élément Mammalde la liste en un Animal, et échouera si la conversion n'est pas réussie.

Si vous aimez plus de contrôle sur le processus de transtypage / conversion, vous pouvez utiliser la ConvertAllméthode de la List<T>classe, qui peut utiliser une expression fournie pour convertir les éléments. Il a l'avantage supplémentaire de renvoyer un List, au lieu de IEnumerable, donc aucun .ToList()n'est nécessaire.

List<object> o = new List<object>();
o.Add("one");
o.Add("two");
o.Add(3);

IEnumerable<string> s1 = o.Cast<string>(); //fails on the 3rd item
List<string> s2 = o.ConvertAll(x => x.ToString()); //succeeds
SWeko
la source
2
Je ne peux pas croire que je n'ai jamais attribué +1 à cette réponse jusqu'à présent. C'est tellement mieux que le mien ci-dessus.
Jamiec
6
@Jamiec Je n'ai pas +1 car il commence par "Non, ce n'est pas possible", tout en enterrant la réponse que beaucoup de ceux qui trouvent cette question recherchent. Techniquement, il a cependant répondu de manière plus approfondie à la question du PO.
Dan Bechard
13

Pour ajouter au point de Sweko:

La raison pour laquelle le casting

var listOfX = new List<X>();
ListOf<Y> ys = (List<Y>)listOfX; // Compile error: Cannot implicitly cast X to Y

n'est pas possible parce que le List<T>est invariant dans le Type T et donc peu importe qu'il Xdérive de Y) - c'est parce que List<T>est défini comme:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T> ... // Other interfaces

(Notez que dans cette déclaration, le type Tici n'a pas de modificateurs de variance supplémentaires)

Cependant, si des collections modifiables ne sont pas requises dans votre conception, une conversion ascendante sur un grand nombre des collections immuables est possible , par exemple à condition qu'elles Giraffedérivent de Animal:

IEnumerable<Animal> animals = giraffes;

En effet, IEnumerable<T>prend en charge la covariance dans T- cela a du sens étant donné qu'il IEnumerableimplique que la collection ne peut pas être modifiée, car elle ne prend pas en charge les méthodes pour ajouter ou supprimer des éléments de la collection. Notez le outmot - clé dans la déclaration de IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Voici une explication supplémentaire de la raison pour laquelle les collections mutables comme Listne peuvent pas prendre en charge covariance, alors que les itérateurs et les collections immuables le peuvent.)

Casting avec .Cast<T>()

Comme d'autres l'ont mentionné, .Cast<T>()peut être appliqué à une collection pour projeter une nouvelle collection d'éléments castés en T, mais cela provoquera un jet InvalidCastExceptionsi la cast sur un ou plusieurs éléments n'est pas possible (ce qui serait le même comportement que de faire l'explicite dans la foreachboucle de l'OP ).

Filtrage et diffusion avec OfType<T>()

Si la liste d'entrée contient des éléments de types différents et incompatibles, le potentiel InvalidCastExceptionpeut être évité en utilisant à la .OfType<T>()place de .Cast<T>(). ( .OfType<>()vérifie si un élément peut être converti en type cible, avant de tenter la conversion, et filtre les types incompatibles.)

pour chaque

Notez également que si l'OP l'a écrit à la place: (notez l' expliciteY y dans le foreach)

List<Y> ListOfY = new List<Y>();

foreach(Y y in ListOfX)
{
    ListOfY.Add(y);
}

que le casting sera également tenté. Cependant, si aucun lancer n'est possible, un InvalidCastExceptionrésultat sera obtenu.

Exemples

Par exemple, étant donné la hiérarchie de classe simple (C # 6):

public abstract class Animal
{
    public string Name { get;  }
    protected Animal(string name) { Name = name; }
}

public class Elephant :  Animal
{
    public Elephant(string name) : base(name){}
}

public class Zebra : Animal
{
    public Zebra(string name)  : base(name) { }
}

Lorsque vous travaillez avec une collection de types mixtes:

var mixedAnimals = new Animal[]
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach(Animal animal in mixedAnimals)
{
     // Fails for Zed - `InvalidCastException - cannot cast from Zebra to Elephant`
     castedAnimals.Add((Elephant)animal);
}

var castedAnimals = mixedAnimals.Cast<Elephant>()
    // Also fails for Zed with `InvalidCastException
    .ToList();

Tandis que:

var castedAnimals = mixedAnimals.OfType<Elephant>()
    .ToList();
// Ellie

filtre uniquement les éléphants - c'est-à-dire que les zèbres sont éliminés.

Re: opérateurs de distribution implicites

Sans opérateurs de conversion dynamiques définis par l'utilisateur, ils ne sont utilisés qu'au moment de la compilation *, donc même si un opérateur de conversion entre Zebra et Elephant, par exemple, était disponible, le comportement d'exécution des approches de conversion ci-dessus ne changerait pas.

Si nous ajoutons un opérateur de conversion pour convertir un zèbre en éléphant:

public class Zebra : Animal
{
    public Zebra(string name) : base(name) { }
    public static implicit operator Elephant(Zebra z)
    {
        return new Elephant(z.Name);
    }
}

Au lieu de cela, étant donné l'opérateur de conversion ci-dessus, le compilateur pourra changer le type du tableau ci-dessous de Animal[]à Elephant[], étant donné que les zèbres peuvent maintenant être convertis en une collection homogène d'éléphants:

var compilerInferredAnimals = new []
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

Utilisation d'opérateurs de conversion implicite lors de l'exécution

* Comme mentionné par Eric, l'opérateur de conversion est cependant accessible au moment de l'exécution en recourant à dynamic:

var mixedAnimals = new Animal[] // i.e. Polymorphic collection
{
    new Zebra("Zed"),
    new Elephant("Ellie")
};

foreach (dynamic animal in mixedAnimals)
{
    castedAnimals.Add(animal);
}
// Returns Zed, Ellie
StuartLC
la source
Hé, je viens d'essayer l'exemple "Utilisation de foreach () pour le filtrage de types" en utilisant: var list = new List <object> () {1, "a", 2, "b", 3, "c", 4, " ré" }; foreach (int i dans la liste) Console.WriteLine (i); et lorsque je l'exécute, j'obtiens "La distribution spécifiée n'est pas valide". Suis-je en train de manquer quelque chose? Je ne pensais pas que foreach fonctionnait de cette façon, c'est pourquoi je l'essayais.
Brent Rittenhouse
De plus, ce n'est pas une chose de type référence contre valeur. Je viens de l'essayer avec une classe de base de «Thing» et deux classes dérivées: «Person» et «Animal». Quand je fais la même chose avec lui, j'obtiens: "Impossible de lancer un objet de type 'Animal' pour taper 'Person'." Il s'agit donc certainement d'itérer à travers chaque élément. SI je devais faire un OfType sur la liste, cela fonctionnerait. ForEach serait probablement très lent s'il devait le vérifier, à moins que le compilateur ne l'ait optimisé.
Brent Rittenhouse
Merci Brent - j'étais hors cours là-bas. foreachne filtre pas, mais l'utilisation d'un type plus dérivé comme variable d'itération contraindra le compilateur à tenter une conversion, qui échouera sur le premier élément non conforme.
StuartLC
7

Vous pouvez utiliser List<Y>.ConvertAll<T>([Converter from Y to T]);

Andrey
la source
3

Ce n'est pas tout à fait la réponse à cette question, mais cela peut être utile pour certains: comme @SWeko l'a dit, grâce à la covariance et à la contravariance, List<X>ne peut pas être intégré List<Y>, mais List<X>peut être intégré à IEnumerable<Y>, et même avec une implicite.

Exemple:

List<Y> ListOfY = new List<Y>();
List<X> ListOfX = (List<X>)ListOfY; // Compile error

mais

List<Y> ListOfY = new List<Y>();
IEnumerable<X> EnumerableOfX = ListOfY;  // No issue

Le gros avantage est qu'il ne crée pas de nouvelle liste en mémoire.

Xav987
la source
1
J'aime cela parce que si vous avez une grande liste de sources, il n'y a pas de performance au début. Au lieu de cela, il y a une petite distribution non perceptible pour chaque entrée traitée par le récepteur. De plus, aucune accumulation de mémoire énorme. parfait pour le traitement des flux.
Johan Franzén
-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
user2361134
la source