Adakah yang bisa menjelaskan kepada saya mengapa saya ingin menggunakan IList over List di C #?
Pertanyaan terkait: Mengapa dianggap buruk untuk diungkapkanList<T>
Adakah yang bisa menjelaskan kepada saya mengapa saya ingin menggunakan IList over List di C #?
Pertanyaan terkait: Mengapa dianggap buruk untuk diungkapkanList<T>
Jawaban:
Jika Anda mengekspos kelas Anda melalui perpustakaan yang akan digunakan orang lain, Anda biasanya ingin mengeksposnya melalui antarmuka daripada implementasi konkret. Ini akan membantu jika Anda memutuskan untuk mengubah implementasi kelas Anda nanti untuk menggunakan kelas konkret yang berbeda. Dalam hal ini pengguna perpustakaan Anda tidak perlu memperbarui kode mereka karena antarmuka tidak berubah.
Jika Anda hanya menggunakannya secara internal, Anda mungkin tidak terlalu peduli, dan menggunakan
List<T>
mungkin tidak masalah.sumber
Jawaban yang kurang populer adalah programmer suka berpura-pura perangkat lunak mereka akan digunakan kembali di seluruh dunia, ketika infact mayoritas proyek akan dikelola oleh sejumlah kecil orang dan betapapun bagusnya soundbite yang berhubungan dengan antarmuka, Anda menipu dirimu sendiri.
Astronot Arsitektur . Kemungkinan Anda akan pernah menulis IList Anda sendiri yang menambahkan apa pun ke yang sudah ada di .NET framework sangat jauh sehingga secara teoritis jeli tots disediakan untuk "praktik terbaik".
Tentunya jika Anda ditanya mana yang Anda gunakan dalam sebuah wawancara, Anda mengatakan IList, tersenyum, dan keduanya terlihat senang pada diri sendiri karena begitu pintar. Atau untuk API yang menghadap publik, IList. Semoga Anda mengerti maksud saya.
sumber
IEnumerable<string>
, di dalam fungsi tersebut saya dapat menggunakan aList<string>
untuk toko dukungan internal untuk menghasilkan koleksi saya, tetapi saya hanya ingin penelepon untuk menyebutkan isinya, bukan menambah atau menghapus. Menerima antarmuka sebagai parameter mengkomunikasikan pesan yang sama "Saya butuh koleksi string, jangan khawatir, saya tidak akan mengubahnya."Antarmuka adalah janji (atau kontrak).
Seperti selalu dengan janji-janji - semakin kecil semakin baik .
sumber
IList
juga janjinya. Manakah janji yang lebih kecil dan lebih baik di sini? Ini tidak masuk akal.List<T>
, karenaAddRange
tidak didefinisikan pada antarmuka.IList<T>
membuat janji yang tidak bisa ditepati. Misalnya, adaAdd
metode yang bisa melempar jikaIList<T>
kebetulan hanya baca.List<T>
di sisi lain selalu dapat ditulis dan dengan demikian selalu menepati janjinya. Juga, sejak .NET 4.6, kami sekarang memilikiIReadOnlyCollection<T>
danIReadOnlyList<T>
yang hampir selalu lebih cocok daripadaIList<T>
. Dan mereka kovarian. HindariIList<T>
!IList<T>
. Seseorang bisa saja menerapkanIReadOnlyList<T>
dan membuat setiap metode melemparkan pengecualian juga. Ini semacam kebalikan dari mengetik bebek - Anda menyebutnya bebek, tapi tidak bisa seperti dukun. Itu adalah bagian dari kontrak, meskipun kompiler tidak dapat menegakkannya. Saya setuju 100%IReadOnlyList<T>
atauIReadOnlyCollection<T>
harus digunakan untuk akses baca saja.Beberapa orang mengatakan "selalu gunakan
IList<T>
bukanList<T>
".Mereka ingin Anda mengubah tanda tangan metode Anda dari
void Foo(List<T> input)
menjadivoid Foo(IList<T> input)
.Orang-orang ini salah.
Lebih bernuansa dari itu. Jika Anda mengembalikan
IList<T>
sebagai bagian dari antarmuka publik ke perpustakaan Anda, Anda meninggalkan sendiri opsi menarik untuk membuat daftar kustom di masa depan. Anda mungkin tidak pernah membutuhkan opsi itu, tetapi ini adalah argumen. Saya pikir itu adalah seluruh argumen untuk mengembalikan antarmuka bukan tipe konkret. Perlu disebutkan, tetapi dalam kasus ini memiliki kelemahan yang serius.Sebagai counterargument kecil, Anda mungkin menemukan bahwa setiap penelepon membutuhkan
List<T>
, dan kode panggilan dipenuhi.ToList()
Tetapi jauh lebih penting, jika Anda menerima IList sebagai parameter Anda sebaiknya berhati-hati, karena
IList<T>
danList<T>
tidak berperilaku seperti itu. Terlepas dari kesamaan nama, dan meskipun berbagi antarmuka, mereka tidak mengekspos kontrak yang sama .Misalkan Anda memiliki metode ini:
Seorang kolega yang membantu "refactor" metode untuk menerima
IList<int>
.Kode Anda sekarang rusak, karena
int[]
mengimplementasikanIList<int>
, tetapi ukurannya tetap. Kontrak untukICollection<T>
(pangkalanIList<T>
) memerlukan kode yang menggunakannya untuk memeriksaIsReadOnly
bendera sebelum mencoba menambah atau menghapus item dari koleksi. Kontrak untukList<T>
tidak.Prinsip Pergantian Liskov (disederhanakan) menyatakan bahwa tipe turunan harus dapat digunakan sebagai pengganti tipe dasar, tanpa prasyarat atau postkondisi tambahan.
Ini terasa seperti melanggar prinsip substitusi Liskov.
Tapi ternyata tidak. Jawabannya adalah bahwa contoh yang digunakan IList <T> / ICollection <T> salah. Jika Anda menggunakan ICollection <T> Anda perlu memeriksa flag IsReadOnly.
Jika seseorang melewati Anda Array atau Daftar, kode Anda akan berfungsi dengan baik jika Anda memeriksa bendera setiap kali dan memiliki mundur ... Tapi sungguh; siapa yang melakukan itu? Apakah Anda tidak tahu sebelumnya jika metode Anda membutuhkan daftar yang dapat mengambil anggota tambahan; tidakkah Anda menyebutkannya dalam tanda tangan metode? Apa sebenarnya yang akan Anda lakukan jika Anda lulus daftar hanya baca seperti
int[]
?Anda dapat mengganti
List<T>
menjadi kode yang menggunakanIList<T>
/ICollection<T>
dengan benar . Anda tidak dapat menjamin bahwa Anda dapat mengganti kode keIList<T>
/ICollection<T>
yang menggunakanList<T>
.Ada seruan untuk Prinsip Tanggung Jawab Tunggal / Prinsip Pemisahan Antarmuka dalam banyak argumen untuk menggunakan abstraksi dan bukan tipe konkret - bergantung pada antarmuka sesempit mungkin. Dalam kebanyakan kasus, jika Anda menggunakan
List<T>
dan Anda pikir Anda bisa menggunakan antarmuka yang lebih sempit - mengapa tidakIEnumerable<T>
? Ini seringkali lebih cocok jika Anda tidak perlu menambahkan item. Jika Anda perlu menambah koleksi, gunakan jenis betonList<T>
,.Bagi saya
IList<T>
(danICollection<T>
) adalah bagian terburuk dari kerangka .NET.IsReadOnly
melanggar prinsip paling tidak mengejutkan. Kelas, sepertiArray
, yang tidak pernah mengizinkan penambahan, penyisipan atau penghapusan item tidak boleh mengimplementasikan antarmuka dengan metode Tambah, Sisipkan dan Hapus. (lihat juga /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )Apakah
IList<T>
cocok untuk organisasi Anda? Jika seorang rekan meminta Anda untuk mengubah metode tanda tangan untuk menggunakanIList<T>
bukanList<T>
, meminta mereka bagaimana mereka akan menambahkan elemen keIList<T>
. Jika mereka tidak tahuIsReadOnly
(dan kebanyakan orang tidak), maka jangan gunakanIList<T>
. Pernah.Perhatikan bahwa flag IsReadOnly berasal dari ICollection <T>, dan menunjukkan apakah item dapat ditambahkan atau dihapus dari koleksi; tetapi hanya untuk benar-benar membingungkan hal-hal, itu tidak menunjukkan apakah mereka dapat diganti, yang dalam kasus Array (yang mengembalikan IsReadOnlys == true) bisa.
Untuk lebih lanjut tentang IsReadOnly, lihat definisi msdn dari ICollection <T> .IsReadOnly
sumber
IsReadOnly
adalahtrue
untukIList
, Anda masih dapat memodifikasi anggota saat ini! Mungkin properti itu harus dinamaiIsFixedSize
?Array
sebagaiIList
, jadi itu sebabnya saya bisa memodifikasi anggota saat ini)IReadOnlyCollection<T>
danIReadOnlyList<T>
yang hampir selalu lebih cocok daripadaIList<T>
tetapi tidak memiliki semantik malas berbahayaIEnumerable<T>
. Dan mereka kovarian. HindariIList<T>
!List<T>
adalah implementasi spesifikIList<T>
, yang merupakan wadah yang dapat dialamatkan dengan cara yang sama seperti array linierT[]
menggunakan indeks integer. Ketika Anda menentukanIList<T>
sebagai jenis argumen metode, Anda hanya menentukan bahwa Anda memerlukan kemampuan tertentu dari wadah.Misalnya, spesifikasi antarmuka tidak memberlakukan struktur data tertentu untuk digunakan. Implementasi
List<T>
terjadi pada kinerja yang sama untuk mengakses, menghapus, dan menambahkan elemen sebagai array linier. Namun, Anda dapat membayangkan implementasi yang didukung oleh daftar tertaut, yang menambahkan elemen pada akhirnya lebih murah (waktu konstan) tetapi akses acak jauh lebih mahal. (Perhatikan bahwa NETLinkedList<T>
tidak tidak melaksanakanIList<T>
.)Contoh ini juga memberi tahu Anda bahwa mungkin ada situasi ketika Anda perlu menentukan implementasinya, bukan antarmuka, dalam daftar argumen: Dalam contoh ini, setiap kali Anda memerlukan karakteristik kinerja akses tertentu. Ini biasanya dijamin untuk implementasi wadah tertentu (
List<T>
dokumentasi: "Ini mengimplementasikanIList<T>
antarmuka generik menggunakan array yang ukurannya secara dinamis ditingkatkan sesuai kebutuhan.").Selain itu, Anda mungkin ingin mempertimbangkan untuk mengekspos fungsionalitas yang paling Anda butuhkan. Sebagai contoh. jika Anda tidak perlu mengubah konten daftar, Anda mungkin harus mempertimbangkan untuk menggunakan
IEnumerable<T>
, yangIList<T>
meluas.sumber
LinkedList<T>
vs mengambilList<T>
vsIList<T>
semua mengkomunikasikan sesuatu dari jaminan kinerja yang diperlukan oleh kode yang dipanggil.Saya akan membalikkan sedikit pertanyaan, alih-alih membenarkan mengapa Anda harus menggunakan antarmuka dibandingkan implementasi konkret, cobalah untuk membenarkan mengapa Anda akan menggunakan implementasi konkret daripada antarmuka. Jika Anda tidak dapat membenarkannya, gunakan antarmuka.
sumber
IList <T> adalah antarmuka sehingga Anda dapat mewarisi kelas lain dan masih menerapkan IList <T> saat mewarisi Daftar <T> mencegah Anda untuk melakukannya.
Misalnya jika ada kelas A dan kelas B Anda mewarisinya maka Anda tidak dapat menggunakan Daftar <T>
sumber
Prinsip TDD dan OOP umumnya adalah pemrograman ke antarmuka bukan implementasi.
Dalam kasus khusus ini karena Anda pada dasarnya berbicara tentang konstruksi bahasa, bukan kebiasaan yang umumnya tidak masalah, tetapi katakan misalnya bahwa Anda menemukan Daftar tidak mendukung sesuatu yang Anda butuhkan. Jika Anda telah menggunakan IList di sisa aplikasi, Anda dapat memperluas Daftar dengan kelas khusus Anda sendiri dan masih dapat meneruskannya tanpa refactoring.
Biaya untuk melakukan ini sangat minim, mengapa tidak menyelamatkan diri dari sakit kepala nanti? Ini adalah prinsip antarmuka.
sumber
Dalam hal ini Anda bisa lulus di kelas mana saja yang mengimplementasikan antarmuka <istar> IList. Jika Anda menggunakan Daftar <Bar> sebagai gantinya, hanya instance Daftar <Bar> yang dapat diteruskan.
Cara <Bar> IList lebih longgar daripada cara <Bar> Daftar.
sumber
Mengejutkan bahwa tidak satu pun dari daftar ini vs. pertanyaan IList (atau jawaban) yang menyebutkan perbedaan tanda tangan. (Itulah sebabnya saya mencari pertanyaan ini di SO!)
Jadi, inilah metode yang terdapat dalam Daftar yang tidak ditemukan di IList, setidaknya pada .NET 4.5 (sekitar 2015)
sumber
Reverse
danToArray
merupakan metode ekstensi untuk antarmuka <n> IEnumerable, yang berasal dari <isti> IList.List
dan bukan implementasi lainnyaIList
.Kasus paling penting untuk menggunakan antarmuka atas implementasi adalah pada parameter untuk API Anda. Jika API Anda mengambil parameter Daftar, maka siapa pun yang menggunakannya harus menggunakan Daftar. Jika tipe parameternya adalah IList, maka pemanggil memiliki lebih banyak kebebasan, dan dapat menggunakan kelas yang tidak pernah Anda dengar, yang bahkan mungkin tidak ada ketika kode Anda ditulis.
sumber
Bagaimana jika NET 5.0 Menggantikan
System.Collections.Generic.List<T>
keSystem.Collection.Generics.LinearList<T>
. .NET selalu memiliki namaList<T>
tetapi mereka menjamin ituIList<T>
adalah kontrak. Jadi IMHO kita (minimal I) tidak seharusnya menggunakan nama seseorang (meskipun. NET dalam hal ini) dan mendapat masalah nanti.Dalam hal menggunakan
IList<T>
, penelepon selalu dijamin hal-hal untuk bekerja, dan pelaksana bebas untuk mengubah koleksi yang mendasarinya untuk setiap implementasi konkret alternatif.IList
sumber
Semua konsep pada dasarnya dinyatakan dalam sebagian besar jawaban di atas mengenai mengapa menggunakan antarmuka dibandingkan implementasi konkret.
IList<T>
Tautan MSDNList<T>
mengimplementasikan sembilan metode tersebut (tidak termasuk metode ekstensi), selain itu ia memiliki sekitar 41 metode publik, yang mempertimbangkan Anda mana yang akan digunakan dalam aplikasi Anda.List<T>
Tautan MSDNsumber
Anda akan karena mendefinisikan IList atau ICollection akan terbuka untuk implementasi lain dari antarmuka Anda.
Anda mungkin ingin memiliki IOrderRepository yang mendefinisikan kumpulan pesanan baik dalam IList atau ICollection. Anda kemudian dapat memiliki berbagai jenis implementasi untuk memberikan daftar pesanan selama itu sesuai dengan "aturan" yang ditentukan oleh IList atau ICollection Anda.
sumber
IList <> hampir selalu lebih disukai sesuai saran poster lain, namun perhatikan ada bug di .NET 3.5 sp 1 saat menjalankan IList <> melalui lebih dari satu siklus serialisasi / deserialisasi dengan WCF DataContractSerializer.
Sekarang ada SP untuk memperbaiki bug ini: KB 971030
sumber
Antarmuka memastikan bahwa Anda setidaknya mendapatkan metode yang Anda harapkan ; menyadari definisi yaitu antarmuka. semua metode abstrak yang ada diimplementasikan oleh kelas apa pun yang mewarisi antarmuka. jadi jika seseorang membuat kelas besar sendiri dengan beberapa metode selain yang ia warisi dari antarmuka untuk beberapa fungsi tambahan, dan itu tidak berguna bagi Anda, lebih baik menggunakan referensi ke subkelas (dalam hal ini antarmuka) dan menetapkan objek kelas konkret untuk itu.
keuntungan tambahan adalah bahwa kode Anda aman dari perubahan apa pun pada kelas beton karena Anda hanya berlangganan sedikit metode kelas beton dan itulah yang akan ada di sana selama kelas beton mewarisi dari antarmuka Anda. menggunakan. jadi keamanannya bagi Anda dan kebebasan bagi pembuat kode yang menulis implementasi konkret untuk mengubah atau menambahkan lebih banyak fungsi ke kelas konkretnya.
sumber
Anda dapat melihat argumen ini dari beberapa sudut termasuk salah satu dari pendekatan OO murni yang mengatakan untuk memprogram terhadap Antarmuka bukan implementasi. Dengan pemikiran ini, menggunakan IList mengikuti prinsipal yang sama seperti berkeliling dan menggunakan Antarmuka yang Anda tetapkan dari awal. Saya juga percaya pada skalabilitas dan faktor fleksibilitas yang disediakan oleh Interface secara umum. Jika kelas mengimplementasikan IList <T> perlu diperluas atau diubah, kode yang digunakan tidak harus berubah; ia tahu apa yang dipatuhi kontrak Antarmuka IList. Namun menggunakan implementasi konkret dan Daftar <T> pada kelas yang berubah, dapat menyebabkan kode panggilan juga perlu diubah. Ini karena kelas yang mematuhi IList <T> menjamin perilaku tertentu yang tidak dijamin oleh tipe konkret menggunakan Daftar <T>.
Juga memiliki kekuatan untuk melakukan sesuatu seperti memodifikasi implementasi standar Daftar <T> pada kelas Menerapkan IList <T> untuk mengatakan .Add, .Remove atau metode IList lainnya memberi pengembang banyak fleksibilitas dan kekuatan, jika tidak ditentukan sebelumnya berdasarkan Daftar <T>
sumber
Biasanya, pendekatan yang baik adalah dengan menggunakan IList di API Anda yang menghadap publik (bila perlu, dan cantumkan semantik), dan kemudian Daftarkan secara internal untuk mengimplementasikan API. Ini memungkinkan Anda untuk mengubah ke implementasi IList yang berbeda tanpa melanggar kode yang menggunakan kelas Anda.
Daftar nama kelas dapat diubah dalam kerangka kerja .net berikutnya tetapi antarmuka tidak akan pernah berubah karena antarmuka kontrak.
Perhatikan bahwa, jika API Anda hanya akan digunakan dalam loop foreach, dll, maka Anda mungkin ingin mempertimbangkan hanya mengekspos IEnumerable saja.
sumber