webentwicklung-frage-antwort-db.com.de

Kürzere Syntax für das Casting von einer Liste <X> zu einer Liste <Y>?

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 Casting durchzuführen).

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 erstellen? Zum Beispiel,

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

Wenn X wirklich in Y umgewandelt werden kann, sollten Sie dies können

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

Einige Dinge, die Sie beachten sollten (H/T für Kommentatoren!)

415
Jamiec

Die direkte Besetzung var ListOfY = (List<Y>)ListOfX ist nicht möglich, da sie co/contravariance vom List<T>-Typ erfordern würde, und das kann nicht in jedem Fall garantiert werden. Bitte lesen Sie weiter, um die Lösungen für dieses Gussproblem 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 etwas. Dies ist syntaktisch gleichbedeutend mit dem Hinzufügen von Code, der nacheinander hinzugefügt wird, da er jede Mammal in der Liste explizit in eine Animal umwandelt. Wenn die Umwandlung nicht erfolgreich ist, schlägt dies fehl.

Wenn Sie mehr Kontrolle über den Casting-/Konvertierungsprozess haben möchten, können Sie die ConvertAll-Methode der List<T>-Klasse verwenden, die einen bereitgestellten Ausdruck zum Konvertieren der Elemente verwenden kann. Es hat den zusätzlichen Nutzen, dass es eine List anstelle von IEnumerable zurückgibt, so dass keine .ToList() notwendig 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
87
SWeko

Zu Swekos Punkt hinzufü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 List<T>invariant im Typ T ist und es daher nicht wichtig ist, ob X von Y abgeleitet ist - dies liegt daran, dass List<T> definiert ist als:

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

(Beachten Sie, dass in dieser Deklaration der Typ T hier keine zusätzlichen Varianzmodifizierer enthält.)

Wenn jedoch in Ihrem Entwurf keine veränderlichen Sammlungen erforderlich sind, ist ein Aktualisierungsvorgang für viele der unveränderlichen Sammlungen möglich, z. vorausgesetzt, dass Giraffe von Animal abgeleitet ist:

IEnumerable<Animal> animals = giraffes;

Dies liegt daran, dass IEnumerable<T> Kovarianz in T unterstützt. Dies ist sinnvoll, da IEnumerable bedeutet, dass die Auflistung nicht geändert werden kann, da sie keine Methoden zum Hinzufügen oder Entfernen von Elementen aus der Auflistung unterstützt. Beachten Sie das Schlüsselwort out in der Deklaration von IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Hier eine weitere Erklärung aus dem Grund, warum veränderliche Sammlungen wie Listcovariance nicht unterstützen können, während unveränderliche Iteratoren und Sammlungen dies können.)

Casting mit .Cast<T>()

Wie andere bereits erwähnt haben, kann .Cast<T>() auf eine Collection angewendet werden, um eine neue Collection von Elementen zu projizieren, die in T gegossen werden. Dies führt jedoch zu einer InvalidCastException, wenn die Umwandlung eines oder mehrerer Elemente nicht möglich ist (was die dasselbe Verhalten wie bei der expliziten Umwandlung in der foreach-Schleife des OP).

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

Wenn die Eingabeliste Elemente unterschiedlicher, nicht kompatibler Typen enthält, kann die potenzielle InvalidCastException durch Verwendung von .OfType<T>() anstelle von .Cast<T>() vermieden werden. (.OfType<>() prüft vor dem Konvertieren, ob ein Element in den Zieltyp konvertiert werden kann, und filtert nicht kompatible Typen heraus.)

für jeden

Beachten Sie auch, wenn das OP stattdessen Folgendes geschrieben hat: (Beachten Sie den expliziten Y 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 kein Cast möglich ist, führt dies zu einer InvalidCastException.

Beispiele

Angenommen, die einfache Klassenhierarchie (C # 6) lautet:

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

Wenn Sie mit einer Sammlung gemischter Typen arbeiten:

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 - d. h. Zebras werden eliminiert.

Re: Implizite Besetzungsoperatoren

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

Wenn Sie 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);
    }
}

Anstelle des obigen Konvertierungsoperators kann der Compiler stattdessen den Typ des unteren Arrays von Animal[] in Elephant[] ändern, da die Zebras jetzt in eine homogene Sammlung von Elefanten konvertiert werden können:

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

Implizite Konvertierungsoperatoren zur Laufzeit verwenden

* Wie von Eric erwähnt, kann auf den Konvertierungsoperator zur Laufzeit zugegriffen werden, indem auf dynamic zugegriffen wird:

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

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

7
Andrey

Dies ist nicht ganz die Antwort auf diese Frage, aber für manche kann es nützlich sein: Wie von @SWeko gesagt wurde, kann List<X> aufgrund von Kovarianz und Kontravarianz nicht in List<Y>, sondern List<X> in IEnumerable<Y> und sogar implizit umgewandelt werden .

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.

0
Xav987