Sintassi più breve per il casting da un Elenco <X> a un Elenco <Y>?


237

So che è possibile lanciare un elenco di elementi da un tipo a un altro (dato che il tuo oggetto ha un metodo di operatore esplicito statico pubblico per eseguire il casting) uno alla volta come segue:

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

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

Ma non è possibile trasmettere l'intero elenco contemporaneamente? Per esempio,

ListOfY = (List<Y>)ListOfX;

@Oded: ho appena provato a renderlo un po 'più chiaro. Non ti preoccupare, non capisci, capisco :)
BoltClock

1
Presumendo che X derivi da Y e Z derivi da Y, pensa cosa succederebbe se aggiungessi Z alla tua lista <Y> che in realtà è una lista <X>.
Richard Friend,

Risposte:


497

Se Xpuò davvero essere lanciato per Yte, dovresti essere in grado di usarlo

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

Alcune cose da tenere presente (H / T ai commentatori!)


12
Prendi un altro distintivo d'oro. Questo è stato abbastanza utile.
ouflak,

6
È necessario includere la seguente riga per consentire al compilatore di riconoscere quei metodi di estensione: using System.Linq;
hypehuman,

8
Inoltre, tieni presente che, sebbene ciò lanci ogni elemento nell'elenco, l'elenco stesso non viene trasmesso; piuttosto viene creato un nuovo elenco con il tipo desiderato.
hypehuman,

4
Inoltre, tenere presente che il Cast<T>metodo non supporta gli operatori di conversione personalizzati. Perché Linq Cast Helper non funziona con l'operatore di cast implicito .
clD

Non funziona per un oggetto che ha un metodo operatore esplicito (framework 4.0)
Adrian,

100

Il cast diretto var ListOfY = (List<Y>)ListOfXnon è possibile perché richiederebbe la co / contravarianza del List<T>tipo e questo non può essere garantito in ogni caso. Continua a leggere per vedere le soluzioni a questo problema di casting.

Mentre sembra normale poter scrivere codice in questo modo:

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

poiché possiamo garantire che ogni mammifero sarà un animale, questo è ovviamente un errore:

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

poiché non tutti gli animali sono un mammifero.

Tuttavia, utilizzando C # 3 e versioni successive, è possibile utilizzare

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

questo facilita un po 'il casting. Questo è sintatticamente equivalente al tuo codice di aggiunta uno a uno, poiché utilizza un cast esplicito per trasmettere ciascuno Mammalnell'elenco in un Animal, e fallirà se il cast non ha esito positivo.

Se desideri un maggiore controllo sul processo di casting / conversione, puoi utilizzare il ConvertAllmetodo della List<T>classe, che può utilizzare un'espressione fornita per convertire gli articoli. Ha il vantaggio aggiunto che restituisce a List, invece di IEnumerable, quindi non .ToList()è necessario.

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
Non posso credere che non abbia mai fatto +1 su questa risposta fino ad ora. È molto meglio del mio sopra.
Jamiec,

6
@Jamiec Non ho fatto +1 perché inizia con "No, non è possibile", mentre seppellisce la risposta che molti cercano questa domanda. Tecnicamente, ha comunque risposto alla domanda del PO in modo più approfondito.
Dan Bechard,

13

Per aggiungere al punto di Sweko:

Il motivo per cui il cast

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

non è possibile perché List<T>è invariante nel tipo T e quindi non importa se Xderiva da Y) - questo perché List<T>è definito come:

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

(Si noti che in questa dichiarazione, digitare Tqui non ha modificatori di varianza aggiuntivi)

Tuttavia, se nel tuo progetto non sono richieste raccolte mutabili, è possibile effettuare l'upgrade a molte delle raccolte immutabili , ad esempio a condizione che Giraffederivi da Animal:

IEnumerable<Animal> animals = giraffes;

Questo perché IEnumerable<T>supporta la covarianza in T- questo ha senso dato che IEnumerableimplica che la collezione non può essere cambiata, poiché non ha supporto per i metodi per aggiungere o rimuovere elementi dalla collezione. Nota la outparola chiave nella dichiarazione di IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Ecco un'ulteriore spiegazione del motivo per cui le collezioni mutabili come Listnon possono supportare covariance, mentre gli iteratori e le raccolte immutabili possono farlo .)

Casting con .Cast<T>()

Come altri hanno già detto, .Cast<T>()può essere applicato a una raccolta per proiettare una nuova raccolta di elementi proiettati su T, tuttavia in tal modo si genererà un InvalidCastExceptioncast se il cast su uno o più elementi non è possibile (il che sarebbe lo stesso comportamento del esplicito cast nel foreachloop dell'OP ).

Filtraggio e trasmissione con OfType<T>()

Se l'elenco di input contiene elementi di tipi diversi e incompatibili, il potenziale InvalidCastExceptionpuò essere evitato utilizzando .OfType<T>()invece di .Cast<T>(). ( .OfType<>()verifica se un elemento può essere convertito nel tipo di destinazione, prima di tentare la conversione, e filtra i tipi incompatibili).

per ciascuno

Si noti inoltre che se l'OP avesse scritto questo: (notare l' esplicitoY y nel foreach)

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

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

che verrà anche tentato il casting. Tuttavia, se non è possibile lanciare un cast, si InvalidCastExceptionotterrà un risultato.

Esempi

Ad esempio, vista la gerarchia di classi semplice (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) { }
}

Quando si lavora con una raccolta di tipi misti:

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

Mentre:

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

filtra solo gli elefanti, ovvero le zebre vengono eliminate.

Ri: operatori cast impliciti

Senza un operatore dinamico, gli operatori di conversione definiti dall'utente vengono utilizzati solo in fase di compilazione *, quindi anche se un operatore di conversione tra dicesse Zebra ed Elephant fosse reso disponibile, il comportamento del runtime sopra descritto degli approcci alla conversione non cambierebbe.

Se aggiungiamo un operatore di conversione per convertire una Zebra in un elefante:

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

Invece, dato l'operatore di conversione sopra, il compilatore sarà in grado di cambiare il tipo dell'array sottostante da Animal[]a Elephant[], dato che le Zebre possono ora essere convertite in una raccolta omogenea di Elefanti:

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

Utilizzo di operatori di conversione implicita in fase di esecuzione

* Come menzionato da Eric, è possibile accedere all'operatore di conversione in fase di esecuzione ricorrendo a 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

Ehi, ho appena provato l'esempio "Utilizzo di foreach () per il filtro dei tipi" utilizzando: var list = new List <oggetto> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i in list) Console.WriteLine (i); e quando lo eseguo ottengo "Il cast specificato non è valido." Mi sto perdendo qualcosa? Non pensavo che foreach funzionasse in questo modo, motivo per cui ci stavo provando.
Brent Rittenhouse,

Inoltre, non è una cosa di riferimento rispetto al tipo di valore. L'ho appena provato con una classe base di "Cosa" e due classi derivate: "Persona" e "Animale". Quando faccio la stessa cosa con esso ottengo: "Impossibile lanciare un oggetto di tipo 'Animale' per digitare 'Persona'." Quindi sta decisamente ripetendo ogni elemento. Se dovessi fare un OfType nell'elenco, funzionerebbe. Ognuno probabilmente sarebbe molto lento se dovesse verificarlo, a meno che il compilatore non lo abbia ottimizzato.
Brent Rittenhouse,

Grazie Brent - Ero fuori rotta lì. foreachnon filtra, ma l'utilizzo di un tipo più derivato come variabile di iterazione costringerà il compilatore a tentare un Cast, che fallirà sul primo elemento che non è conforme.
StuartLC,

7

Puoi usare List<Y>.ConvertAll<T>([Converter from Y to T]);


3

Questa non è proprio la risposta a questa domanda, ma può essere utile per alcuni: come ha detto @SWeko, grazie alla covarianza e alla contraddizione, List<X>non può essere inserito List<Y>, ma List<X>può essere inserito IEnumerable<Y>e persino con un cast implicito.

Esempio:

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

ma

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

Il grande vantaggio è che non crea un nuovo elenco in memoria.


1
Mi piace questo perché se si dispone di un ampio elenco di fonti, all'inizio non si riscontra alcun impatto sulle prestazioni. Invece c'è un piccolo cast non evidente per ogni voce che viene elaborata dal ricevitore. Inoltre non si accumula memoria enorme. perfetto per l'elaborazione di flussi.
Johan Franzén,

-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.