web-dev-qa-db-fra.com

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

Je sais qu'il est possible de convertir une liste d'éléments d'un type à un autre (étant donné que votre objet dispose d'une méthode d'opérateur explicite statique publique pour effectuer la conversion), l'un après l'autre, 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 toute la liste en même temps? Par exemple,

ListOfY = (List<Y>)ListOfX;
195
Jimbo

Si X peut vraiment être converti en Y, vous devriez pouvoir utiliser

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

Certaines choses à prendre en compte (H/T aux commentateurs!)

415
Jamiec

La diffusion directe var ListOfY = (List<Y>)ListOfX n'est pas possible car elle nécessiterait co/contravariance de type List<T> et cela ne peut tout simplement pas être garanti. S'il vous plaît lisez la suite pour voir les solutions à ce problème de casting.

Bien qu'il semble normal de pouvoir écrire du code comme celui-ci:

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;

puisque 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>();

ça facilite un peu le casting. Cela équivaut syntaxiquement à votre code d’ajout un par un, car il utilise un transtypage explicite pour transformer chaque Mammal de la liste en un Animal, et échouera si le transfert n’est pas réussi.

Si vous souhaitez davantage de contrôle sur le processus de conversion/conversion, vous pouvez utiliser la méthode ConvertAll de la classe List<T>, qui peut utiliser une expression fournie pour convertir les éléments. Il a l'avantage supplémentaire de renvoyer List, au lieu de IEnumerable, de sorte qu'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
87
SWeko

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, c'est parce que le List<T> est invariant dans le Type T et qu'il importe donc que X dérive de Y) - c'est parce que List<T> est défini comme suit:

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

(Notez que dans cette déclaration, le type T n'a pas de modificateur de variance supplémentaire)

Toutefois, si votre conception ne nécessite pas de collections modifiables, vous pouvez effectuer une conversion ascendante sur de nombreuses collections immuables, est possible, par exemple. à condition que Giraffe dérive de Animal:

IEnumerable<Animal> animals = giraffes;

Ceci s'explique par le fait que IEnumerable<T> prend en charge la covariance dans T. Cela est logique étant donné que IEnumerable implique que la collection ne peut pas être modifiée, car elle ne prend pas en charge les méthodes permettant d'ajouter ou de supprimer des éléments de la collection. Notez le mot clé out dans la déclaration de IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Voici une explication supplémentaire pour la raison pour laquelle les collections modifiables telles que List ne peuvent pas supporter covariance, alors que les itérateurs et collections immuables le peuvent.)

Casting avec .Cast<T>()

Comme d'autres l'ont déjà mentionné, .Cast<T>() peut être appliqué à une collection pour projeter une nouvelle collection d'éléments fondus en T, mais cela produira une InvalidCastException si la distribution sur un ou plusieurs éléments n'est pas possible même comportement que lors de la conversion explicite dans la boucle foreach du OP).

Filtrage et transtypage avec OfType<T>()

Si la liste d'entrée contient des éléments de types différents et incompatibles, vous pouvez éviter la variable InvalidCastException en utilisant .OfType<T>() au lieu 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 le PO a écrit ceci à la place: (notez le explicit Y y dans la foreach)

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

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

que le casting sera également tenté. Cependant, si aucun transtypage n'est possible, il en résultera une InvalidCastException.

Exemples

Par exemple, étant donné la simple hiérarchie de classes (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 mélangés:

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 dynamique, les opérateurs de conversion définis par l'utilisateur ne sont utilisés que par compile-time *. Ainsi, même si un opérateur de conversion entre, par exemple, Zebra et Elephant, était mis à disposition, le comportement d'exécution précédent des approches de conversion 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 des opérateurs de conversion implicites au moment de l'exécution

* Comme indiqué par Eric, l'opérateur de conversion est toutefois accessible au moment de l'exécution en ayant recours à 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
9
StuartLC

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

7
Andrey

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 contradiction, List<X> ne peut pas être converti en List<Y>, mais List<X> peut être converti en IEnumerable<Y>, et même avec une conversion 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.

0
Xav987