Kürzere Syntax für das Umwandeln von einer Liste <X> in eine Liste <Y>?


236

Ich weiß, dass es möglich ist, eine Liste von Elementen von einem Typ in einen anderen umzuwandeln (vorausgesetzt, Ihr Objekt verfügt über eine öffentliche statische explizite Operatormethode, um das Umwandeln durchzuführen), und zwar wie folgt:

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

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

Aber ist es nicht möglich, die gesamte Liste auf einmal zu besetzen? Beispielsweise,

ListOfY = (List<Y>)ListOfX;

@Oded: Ich habe nur versucht, das etwas klarer zu machen. Mach dir keine Sorgen, ich verstehe nicht :)
BoltClock

1
Angenommen, X leitet sich von Y ab und Z leitet sich von Y ab. Überlegen Sie, was passieren würde, wenn Sie Z zu Ihrer Liste <Y> hinzufügen, die wirklich eine Liste <X> ist.
Richard Friend

Antworten:


496

Wenn Xwirklich gegossen werden Ykann, sollten Sie in der Lage sein, zu verwenden

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

Einige Dinge zu beachten (H / T an Kommentatoren!)


12
Habe noch ein goldenes Abzeichen. Das war sehr nützlich.
Ouflak

6
Muss die folgende Zeile enthalten, damit der Compiler diese Erweiterungsmethoden erkennt: using System.Linq;
Hypehuman

8
Beachten Sie auch, dass die Liste selbst nicht gegossen wird, obwohl dadurch jedes Element in der Liste gewirkt wird. Vielmehr wird eine neue Liste mit dem gewünschten Typ erstellt.
Hypehuman

4
Beachten Sie auch, dass die Cast<T>Methode keine benutzerdefinierten Konvertierungsoperatoren unterstützt. Warum funktioniert die Linq Guss Helper keine Arbeit mit dem impliziten Cast Operator .
ClD

Es funktioniert nicht für ein Objekt, das eine explizite Operatormethode (Framework 4.0) hat
Adrian

100

Die direkte Besetzung var ListOfY = (List<Y>)ListOfXist nicht möglich, da dies eine Co / Contravarianz des List<T>Typs erfordern würde , und dies kann einfach nicht in jedem Fall garantiert werden. Bitte lesen Sie weiter, um die Lösungen für dieses Casting-Problem zu sehen.

Während es normal erscheint, Code wie folgt schreiben zu können:

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

Da wir garantieren können, dass jedes Säugetier ein Tier ist, ist dies offensichtlich ein Fehler:

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

da nicht jedes Tier ein Säugetier ist.

Mit C # 3 und höher können Sie jedoch verwenden

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

das erleichtert das Casting ein wenig. Dies entspricht syntaktisch Ihrem nacheinander hinzugefügten Code, da er eine explizite Umwandlung verwendet, um jede Mammalin der Liste in eine Animalumzuwandeln, und schlägt fehl, wenn die Umwandlung nicht erfolgreich ist.

Wenn Sie mehr Kontrolle über den Casting- / Konvertierungsprozess wünschen, können Sie die ConvertAllMethode der List<T>Klasse verwenden, die einen angegebenen Ausdruck zum Konvertieren der Elemente verwenden kann. Es hat den zusätzlichen Vorteil, dass es a Listanstelle von zurückgibt IEnumerable, sodass kein .ToList()erforderlich ist.

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

2
Ich kann nicht glauben, dass ich diese Antwort bis jetzt noch nie gegeben habe. Es ist so viel besser als meins oben.
Jamiec

6
@Jamiec Ich habe nicht +1, weil er mit "Nein, es ist nicht möglich" beginnt, während er die Antwort vergräbt, nach der viele suchen, die diese Frage finden. Technisch gesehen beantwortete er die Frage des OP jedoch gründlicher.
Dan Bechard

13

Um zu Swekos Punkt hinzuzufügen:

Der Grund warum die Besetzung

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

ist nicht möglich, weil das im Typ T invariantList<T> ist und es daher keine Rolle spielt, ob es von ) stammt - dies liegt daran, dass definiert ist als:XYList<T>

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

(Beachten Sie, dass in dieser Deklaration der Typ Thier keine zusätzlichen Varianzmodifikatoren enthält.)

Wenn jedoch wandelbar Sammlungen sind nicht in Ihrem Design erforderlich, eine upcast auf vielen der unveränderlichen Sammlungen, ist möglich , zum Beispiel vorgesehen , dass Giraffeergibt sich aus Animal:

IEnumerable<Animal> animals = giraffes;

Dies liegt daran, dass IEnumerable<T>die Kovarianz in unterstützt wird. TDies ist sinnvoll, IEnumerableda die Auflistung nicht geändert werden kann, da Methoden zum Hinzufügen oder Entfernen von Elementen zur Auflistung nicht unterstützt werden. Beachten Sie das outSchlüsselwort in der Deklaration von IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Hier ist eine weitere Erklärung für den Grund, warum veränderbare Sammlungen wie Listnicht unterstützt werden können covariance, während unveränderliche Iteratoren und Sammlungen dies können.)

Casting mit .Cast<T>()

Wie andere bereits erwähnt haben, .Cast<T>()kann es auf eine Sammlung angewendet werden, um eine neue Sammlung von Elementen zu projizieren, die in T umgewandelt wurden. Dies führt jedoch zu einem, InvalidCastExceptionwenn die Umwandlung in ein oder mehrere Elemente nicht möglich ist (was das gleiche Verhalten wie beim expliziten Ausführen wäre in die foreachSchleife des OP geworfen ).

Filtern und Gießen mit OfType<T>()

Wenn die Eingabeliste Elemente unterschiedlicher, inkompatibler Typen enthält, kann das Potenzial InvalidCastExceptiondurch Verwendung von .OfType<T>()anstelle von vermieden werden .Cast<T>(). ( .OfType<>()Überprüft vor dem Versuch der Konvertierung, ob ein Element in den Zieltyp konvertiert werden kann, und filtert inkompatible Typen heraus.)

für jedes

Beachten Sie auch, dass, wenn das OP dies stattdessen geschrieben hätte: (beachten Sie das expliziteY y in der foreach)

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

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

dass das Casting auch versucht wird. Wenn jedoch keine Besetzung möglich ist, InvalidCastExceptionergibt sich ein Wille.

Beispiele

Zum Beispiel angesichts der einfachen (C # 6) Klassenhierarchie:

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) { }
}

Bei der Arbeit mit einer Sammlung gemischter Typen:

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

Wohingegen:

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

filtert nur die Elefanten heraus - dh Zebras werden eliminiert.

Betreff: Implizite Cast-Operatoren

Ohne dynamische, benutzerdefinierte Konvertierungsoperatoren werden nur zur Kompilierungszeit * verwendet. Selbst wenn ein Konvertierungsoperator zwischen beispielsweise Zebra und Elephant verfügbar wäre, würde sich das obige Laufzeitverhalten der Konvertierungsansätze nicht ändern.

Wenn wir einen Konvertierungsoperator hinzufügen, um ein Zebra in einen Elefanten zu konvertieren:

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

Stattdessen angesichts der obigen Umwandlungsoperator, wird der Compiler in der Lage sein , den Typ des nachfolgend Array ändern aus Animal[]zu Elephant[]bestimmten, dass die Zebras kann nun zu einer homogenen Ansammlung von Elefanten umgewandelt werden:

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

Verwenden impliziter Konvertierungsoperatoren zur Laufzeit

* Wie von Eric erwähnt, kann auf den Konvertierungsoperator jedoch zur Laufzeit zugegriffen werden, indem auf Folgendes zurückgegriffen wird 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

Hey, ich habe gerade das Beispiel "Verwenden von foreach () zur Typfilterung" mit: var list = new List <Objekt> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i in list) Console.WriteLine (i); und wenn ich es starte, bekomme ich "Angegebene Besetzung ist ungültig." Vermisse ich etwas Ich dachte nicht, dass foreach so funktioniert, weshalb ich es versucht habe.
Brent Rittenhouse

Es ist auch keine Referenz- oder Werttypsache. Ich habe es gerade mit einer Basisklasse von 'Thing' und zwei abgeleiteten Klassen versucht: 'Person' und 'Animal'. Wenn ich dasselbe damit mache, erhalte ich: "Objekt vom Typ 'Tier' kann nicht in Typ 'Person' umgewandelt werden." Es durchläuft also definitiv jedes Element. Wenn ich einen OfType auf der Liste machen würde, würde es funktionieren. ForEach wäre wahrscheinlich sehr langsam, wenn es dies überprüfen müsste, es sei denn, der Compiler hat es optimiert.
Brent Rittenhouse

Danke Brent - ich war dort vom Kurs abgekommen. foreachfiltert nicht, aber die Verwendung eines stärker abgeleiteten Typs als Iterationsvariable zwingt den Compiler dazu, einen Cast zu versuchen, der für das erste Element fehlschlägt, das nicht den Anforderungen entspricht.
StuartLC

7

Sie können verwenden List<Y>.ConvertAll<T>([Converter from Y to T]);


3

Dies ist nicht ganz die Antwort auf diese Frage, aber es kann für einige nützlich sein: Wie @SWeko sagte, kann dank Kovarianz und Kontravarianz List<X>nicht eingegossen werden List<Y>, sondern List<X>kann hineingegossen werden IEnumerable<Y>, und sogar mit impliziter Besetzung.

Beispiel:

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

aber

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

Der große Vorteil ist, dass keine neue Liste im Speicher erstellt wird.


1
Ich mag das, denn wenn Sie eine große Quellenliste haben, gibt es am Anfang keinen Leistungseinbruch. Stattdessen gibt es für jeden Eintrag, der vom Empfänger verarbeitet wird, eine kleine, nicht wahrnehmbare Besetzung. Auch kein riesiger Speicheraufbau. Perfekt für die Verarbeitung von Streams.
Johan Franzén

-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.