Sintaks yang lebih pendek untuk casting dari Daftar <X> ke Daftar <Y>?

237

Saya tahu ini mungkin untuk melemparkan daftar item dari satu jenis ke yang lain (mengingat bahwa objek Anda memiliki metode operator publik statis statis untuk melakukan casting) satu per satu sebagai berikut:

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

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

Tetapi apakah tidak mungkin untuk melemparkan seluruh daftar sekaligus? Sebagai contoh,

ListOfY = (List<Y>)ListOfX;
Jimbo
sumber
@ Oded: Saya hanya mencoba membuatnya lebih jelas. Jangan khawatir, Anda tidak, saya mengerti :)
BoltClock
1
Anggap X berasal dari Y, dan Z berasal dari Y, pikirkan apa yang akan terjadi jika Anda menambahkan Z ke Daftar Anda <Y> yang benar-benar Daftar <X>.
Richard Friend

Jawaban:

497

Jika Xbenar-benar dapat dilemparkan ke YAnda harus dapat menggunakan

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

Beberapa hal yang harus diperhatikan (H / T kepada komentator!)

Jamiec
sumber
12
Punya lencana emas lain. Ini cukup berguna.
ouflak
6
Harus menyertakan baris berikut untuk membuat kompiler mengenali metode ekstensi tersebut: using System.Linq;
hypehuman
8
Perlu diketahui juga bahwa meskipun ini membuang setiap item dalam daftar, daftar itu sendiri tidak dicasting; melainkan daftar baru dibuat dengan tipe yang diinginkan.
hypehuman
4
Perlu diketahui juga bahwa Cast<T>metode tidak mendukung operator konversi khusus. Mengapa Linq Cast Helper Tidak Bekerja dengan Operator Cast Implisit .
CLD
Ini tidak berfungsi untuk objek yang memiliki metode operator eksplisit (kerangka 4.0)
Adrian
100

Pemain langsung var ListOfY = (List<Y>)ListOfXtidak mungkin karena akan membutuhkan co / contravariance dari List<T>jenis, dan itu tidak dapat dijamin dalam setiap kasus. Baca terus untuk melihat solusi untuk masalah casting ini.

Meskipun tampaknya normal untuk dapat menulis kode seperti ini:

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

karena kami dapat menjamin bahwa setiap mamalia akan menjadi binatang, ini jelas sebuah kesalahan:

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

karena tidak setiap binatang adalah mamalia.

Namun, menggunakan C # 3 dan di atas, Anda dapat menggunakan

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

yang memudahkan casting sedikit. Ini secara sintaksis setara dengan kode Anda menambahkan satu-per-satu, karena menggunakan pemeran eksplisit untuk masing-masing melemparkan Mammaldalam daftar ke Animal, dan akan gagal jika pemeran tidak berhasil.

Jika Anda suka kontrol lebih besar atas proses casting / konversi, Anda bisa menggunakan ConvertAllmetode List<T>kelas, yang bisa menggunakan ekspresi yang disediakan untuk mengonversi item. Ini memiliki manfaat tambahan yang mengembalikan List, bukannya IEnumerable, jadi tidak .ToList()perlu.

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
sumber
2
Saya tidak percaya saya tidak pernah memberi +1 jawaban ini sampai sekarang. Jauh lebih baik daripada milik saya di atas.
Jamiec
6
@Jamiec Saya tidak memberi +1 karena ia mulai dengan "Tidak, itu tidak mungkin", sambil mengubur jawaban yang dicari banyak orang. Secara teknis, dia menjawab pertanyaan OP lebih teliti.
Dan Bechard
13

Untuk menambah poin Sweko:

Alasan mengapa para pemain

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

tidak mungkin karena List<T>adalah invarian dalam Type T dan dengan demikian tidak peduli apakah Xatau diperoleh dari Y) - ini karena List<T>didefinisikan sebagai:

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

(Perhatikan bahwa dalam deklarasi ini, ketik di Tsini tidak memiliki pengubah varian tambahan)

Namun, jika koleksi bisa berubah tidak diperlukan dalam desain Anda, sebuah upcast pada banyak koleksi berubah, mungkin , misalnya asalkan Giraffeatau diperoleh dari Animal:

IEnumerable<Animal> animals = giraffes;

Ini karena IEnumerable<T>mendukung kovarian dalam T- ini masuk akal mengingat bahwa IEnumerablemenyiratkan koleksi tidak dapat diubah, karena tidak memiliki dukungan untuk metode untuk Menambahkan atau Menghapus elemen dari koleksi. Perhatikan outkata kunci dalam deklarasi IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable

( Berikut penjelasan lebih lanjut untuk alasan mengapa koleksi yang bisa berubah seperti Listtidak dapat mendukung covariance, sedangkan iterator dan koleksi yang tidak dapat diubah dapat.)

Casting dengan .Cast<T>()

Seperti yang telah disebutkan orang lain, .Cast<T>()dapat diterapkan pada koleksi untuk memproyeksikan koleksi baru elemen InvalidCastExceptionyang dilemparkan ke T, namun hal itu akan melempar jika cor pada satu atau lebih elemen tidak mungkin (yang akan menjadi perilaku yang sama seperti melakukan eksplisit masukkan dalam foreachloop OP ).

Filter dan Casting dengan OfType<T>()

Jika daftar input berisi elemen-elemen dengan tipe yang berbeda dan tidak dapat dilawan, potensi InvalidCastExceptiondapat dihindari dengan menggunakan .OfType<T>()alih-alih .Cast<T>(). ( .OfType<>()memeriksa untuk melihat apakah suatu elemen dapat dikonversi ke jenis target, sebelum mencoba konversi, dan memfilter jenis yang tidak dapat dilompati.)

untuk setiap

Perhatikan juga bahwa jika OP yang menulis ini sebagai gantinya: (catat eksplisitY y di foreach)

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

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

bahwa casting juga akan dicoba. Namun, jika tidak ada pemeran yang mungkin, InvalidCastExceptionhasil akan.

Contohnya

Misalnya, dengan hierarki kelas sederhana (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) { }
}

Saat bekerja dengan koleksi jenis campuran:

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

Sedangkan:

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

hanya menyaring Gajah - yaitu Zebra dihilangkan.

Re: Operator pemain implisit

Tanpa dinamis, operator konversi yang ditentukan pengguna hanya digunakan pada waktu kompilasi *, jadi bahkan jika operator konversi antara katakanlah Zebra dan Elephant tersedia, perilaku waktu berjalan di atas dari pendekatan konversi tidak akan berubah.

Jika kami menambahkan operator konversi untuk mengubah Zebra menjadi Gajah:

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

Alih-alih, mengingat operator konversi di atas, kompiler akan dapat mengubah jenis array di bawah ini dari Animal[]menjadi Elephant[], mengingat bahwa Zebra sekarang dapat dikonversi ke koleksi Gajah yang homogen:

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

Menggunakan Operator Konversi Implisit pada waktu berjalan

* Seperti yang disebutkan oleh Eric, operator konversi dapat diakses pada waktu berjalan dengan beralih ke 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
sumber
Hai, saya baru saja mencoba contoh "Menggunakan foreach () untuk tipe filtering" menggunakan: var list = Daftar baru <object> () {1, "a", 2, "b", 3, "c", 4, " d "}; foreach (int i dalam daftar) Console.WriteLine (i); dan ketika saya menjalankannya saya mendapatkan "Pemain yang dispesifikasikan tidak valid." Apakah saya melewatkan sesuatu? Saya tidak berpikir bahwa setiap jalan bekerja dengan cara ini, itulah sebabnya saya mencobanya.
Brent Rittenhouse
Juga, itu bukan referensi vs jenis nilai. Saya baru saja mencobanya dengan kelas dasar 'Benda' dan dua kelas turunan: 'Orang', dan 'Hewan'. Ketika saya melakukan hal yang sama dengan itu saya mendapatkan: "Tidak dapat membuang objek bertipe 'Hewan' untuk mengetik 'Orang'." Jadi sudah pasti iterasi melalui setiap elemen. JIKA saya melakukan OfType pada daftar maka itu akan berhasil. ForEach mungkin akan sangat lambat jika harus memeriksa ini, kecuali jika kompilator mengoptimalkannya.
Brent Rittenhouse
Terima kasih Brent - Saya tentunya di sana. foreachtidak memfilter, tetapi menggunakan tipe yang lebih diturunkan sebagai variabel iterasi akan memaksa kompiler untuk mencoba Cast, yang akan gagal pada elemen pertama yang tidak mematuhinya.
StuartLC
7

Kamu bisa memakai List<Y>.ConvertAll<T>([Converter from Y to T]);

Andrey
sumber
3

Ini bukan jawaban yang tepat untuk pertanyaan ini, tetapi mungkin bermanfaat bagi beberapa orang: seperti yang dikatakan @SWeko, terima kasih kepada kovarians dan contravariance, List<X>tidak dapat dilemparkan ke dalam List<Y>, tetapi List<X>dapat dilemparkan ke dalam IEnumerable<Y>, dan bahkan dengan pemeran implisit.

Contoh:

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

tapi

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

Keuntungan besar adalah tidak membuat daftar baru di memori.

Xav987
sumber
1
Saya suka ini karena jika Anda memiliki daftar sumber yang besar, pada awalnya tidak ada kinerja. Sebagai gantinya ada gips kecil yang tidak terlihat untuk setiap entri yang sedang diproses oleh penerima. Juga tidak ada memori besar yang menumpuk. sempurna untuk memproses streaming.
Johan Franzén
-2
dynamic data = List<x> val;  
List<y> val2 = ((IEnumerable)data).Cast<y>().ToList();
pengguna2361134
sumber