Meskipun kita dapat mewarisi dari kelas dasar / antarmuka, mengapa kita tidak dapat mendeklarasikan List<>
menggunakan kelas / antarmuka yang sama?
interface A
{ }
class B : A
{ }
class C : B
{ }
class Test
{
static void Main(string[] args)
{
A a = new C(); // OK
List<A> listOfA = new List<C>(); // compiler Error
}
}
Apakah ada jalan keluar?
Jawaban:
Cara untuk membuat pekerjaan ini adalah untuk beralih ke daftar dan melemparkan elemen. Ini dapat dilakukan dengan menggunakan ConvertAll:
Anda juga bisa menggunakan Linq:
sumber
ConvertAll
atauCast
?Pertama-tama, berhentilah menggunakan nama-nama kelas yang mustahil untuk dipahami seperti A, B, C. Gunakan Hewan, Mamalia, Jerapah, atau Makanan, Buah, Jeruk atau sesuatu di mana hubungannya jelas.
Pertanyaan Anda kemudian adalah "mengapa saya tidak bisa menetapkan daftar jerapah ke variabel daftar jenis hewan, karena saya bisa menetapkan jerapah ke variabel jenis hewan?"
Jawabannya adalah: seandainya Anda bisa. Lalu apa yang bisa salah?
Nah, Anda dapat menambahkan Macan ke daftar hewan. Misalkan kami mengizinkan Anda untuk meletakkan daftar jerapah dalam variabel yang menyimpan daftar hewan. Kemudian Anda mencoba menambahkan harimau ke daftar itu. Apa yang terjadi? Apakah Anda ingin daftar jerapah mengandung harimau? Apakah Anda ingin crash? atau apakah Anda ingin kompiler melindungi Anda dari crash dengan menjadikan tugas itu ilegal?
Kami memilih yang terakhir.
Konversi semacam ini disebut konversi "kovarian". Dalam C # 4 kami akan memungkinkan Anda untuk membuat konversi kovarian pada antarmuka dan delegasi ketika konversi diketahui selalu aman . Lihat artikel blog saya tentang kovarians dan contravariance untuk detailnya. (Akan ada yang baru pada topik ini pada hari Senin dan Kamis minggu ini.)
sumber
IEnumerable
bukanList
? yaitu:List<Animal> listAnimals = listGiraffes as List<Animal>;
tidak mungkin, tetapiIEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>
berfungsi.IEnumerable<T>
danIEnumerator<T>
keduanya ditandai sebagai aman untuk kovarians, dan kompiler telah memverifikasi itu.Mengutip penjelasan hebat Eric
Tetapi bagaimana jika Anda ingin memilih untuk crash runtime daripada kesalahan kompilasi? Anda biasanya menggunakan Cast <> atau ConvertAll <> tetapi kemudian Anda akan memiliki 2 masalah: Ini akan membuat salinan daftar. Jika Anda menambahkan atau menghapus sesuatu di daftar baru, ini tidak akan tercermin dalam daftar asli. Dan kedua, ada performa besar dan hukuman memori karena membuat daftar baru dengan objek yang ada.
Saya memiliki masalah yang sama dan karena itu saya membuat kelas pembungkus yang dapat membuat daftar generik tanpa membuat daftar yang sama sekali baru.
Dalam pertanyaan awal, Anda dapat menggunakan:
dan di sini kelas wrapper (+ metode extention CastList agar mudah digunakan)
sumber
Jika Anda menggunakan
IEnumerable
sebagai gantinya, itu akan berfungsi (setidaknya dalam C # 4.0, saya belum mencoba versi sebelumnya). Ini hanya pemain, tentu saja, itu masih akan menjadi daftar.Dari pada -
List<A> listOfA = new List<C>(); // compiler Error
Dalam kode asli pertanyaan, gunakan -
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
sumber
List<A> listOfA = new List<C>(); // compiler Error
dalam kode asli pertanyaan, masukkanIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
Sejauh mengapa itu tidak berhasil, mungkin akan membantu untuk memahami kovarians dan contravariance .
Hanya untuk menunjukkan mengapa ini tidak berhasil, berikut adalah perubahan pada kode yang Anda berikan:
Haruskah ini berhasil? Item pertama dalam daftar adalah tipe "B", tetapi tipe item DerivedList adalah C.
Sekarang, asumsikan kita benar-benar hanya ingin membuat fungsi generik yang beroperasi pada daftar beberapa jenis yang mengimplementasikan A, tetapi kami tidak peduli apa jenisnya:
sumber
Anda hanya dapat memilih untuk membaca daftar saja. Sebagai contoh:
Dan Anda tidak dapat melakukannya untuk daftar yang mendukung elemen hemat. Alasannya adalah:
Apa sekarang? Ingat bahwa listObject dan listString sebenarnya adalah daftar yang sama, jadi listString sekarang memiliki elemen objek - seharusnya tidak mungkin dan tidak.
sumber
Saya pribadi suka membuat lib dengan ekstensi ke kelas
sumber
Karena C # tidak mengizinkan jenis itu
warisankonversi saat ini .sumber
Ini adalah perpanjangan dari jawaban brilian BigJim .
Dalam kasus saya, saya memiliki
NodeBase
kelas denganChildren
kamus, dan saya membutuhkan cara untuk melakukan O (1) secara umum dari anak-anak. Saya mencoba untuk mengembalikan bidang kamus pribadi di pengambilChildren
, jadi jelas saya ingin menghindari penyalinan / iterasi yang mahal. Oleh karena itu saya menggunakan kode Bigjim untuk melemparkanDictionary<whatever specific type>
ke generikDictionary<NodeBase>
:Ini bekerja dengan baik. Namun, saya akhirnya mengalami keterbatasan yang tidak terkait dan akhirnya menciptakan
FindChild()
metode abstrak di kelas dasar yang akan melakukan pencarian. Ternyata ini menghilangkan kebutuhan akan kamus yang sudah dicor. (Saya bisa menggantinya dengan yang sederhanaIEnumerable
untuk tujuan saya.)Jadi pertanyaan yang mungkin Anda tanyakan (terutama jika kinerja adalah masalah yang melarang Anda menggunakan
.Cast<>
atau.ConvertAll<>
) adalah:"Apakah saya benar-benar perlu membuang seluruh koleksi, atau bisakah saya menggunakan metode abstrak untuk menyimpan pengetahuan khusus yang diperlukan untuk melakukan tugas dan dengan demikian menghindari mengakses koleksi secara langsung?"
Terkadang solusi paling sederhana adalah yang terbaik.
sumber
Anda juga dapat menggunakan
System.Runtime.CompilerServices.Unsafe
paket NuGet untuk membuat referensi yang samaList
:Dengan contoh di atas, Anda dapat mengakses
Hammer
instance yang ada dalam daftar menggunakantools
variabel. MenambahkanTool
instance ke daftar melemparArrayTypeMismatchException
pengecualian karenatools
referensi variabel yang sama denganhammers
.sumber
Saya telah membaca seluruh utas ini, dan saya hanya ingin menunjukkan apa yang tampak seperti inkonsistensi bagi saya.
Kompiler mencegah Anda dari melakukan tugas dengan Daftar:
Tetapi kompiler sangat baik dengan array:
Argumen tentang apakah tugas tersebut diketahui aman jatuh terpisah di sini. Tugas yang saya lakukan dengan array tidak aman . Untuk membuktikannya, jika saya tindak lanjuti dengan ini:
Saya mendapatkan pengecualian runtime "ArrayTypeMismatchException". Bagaimana seseorang menjelaskan hal ini? Jika kompiler benar-benar ingin mencegah saya melakukan sesuatu yang bodoh, itu seharusnya mencegah saya dari melakukan tugas array.
sumber