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.
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.
IEnumerable<string>
, di dalam fungsi tersebut saya dapat menggunakan a List<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 .
IList
juga janjinya. Manakah janji yang lebih kecil dan lebih baik di sini? Ini tidak masuk akal.
List<T>
, karena AddRange
tidak didefinisikan pada antarmuka.
IList<T>
membuat janji yang tidak bisa ditepati. Misalnya, ada Add
metode yang bisa melempar jika IList<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 memiliki IReadOnlyCollection<T>
dan IReadOnlyList<T>
yang hampir selalu lebih cocok daripada IList<T>
. Dan mereka kovarian. Hindari IList<T>
!
IList<T>
. Seseorang bisa saja menerapkan IReadOnlyList<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>
atau IReadOnlyCollection<T>
harus digunakan untuk akses baca saja.
Beberapa orang mengatakan "selalu gunakan IList<T>
bukan List<T>
".
Mereka ingin Anda mengubah tanda tangan metode Anda dari void Foo(List<T> input)
menjadi void 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>
dan List<T>
tidak berperilaku seperti itu. Terlepas dari kesamaan nama, dan meskipun berbagi antarmuka, mereka tidak mengekspos kontrak yang sama .
Misalkan Anda memiliki metode ini:
public Foo(List<int> a)
{
a.Add(someNumber);
}
Seorang kolega yang membantu "refactor" metode untuk menerima IList<int>
.
Kode Anda sekarang rusak, karena int[]
mengimplementasikan IList<int>
, tetapi ukurannya tetap. Kontrak untuk ICollection<T>
(pangkalan IList<T>
) memerlukan kode yang menggunakannya untuk memeriksa IsReadOnly
bendera sebelum mencoba menambah atau menghapus item dari koleksi. Kontrak untuk List<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.
int[] array = new[] {1, 2, 3};
IList<int> ilist = array;
ilist.Add(4); // throws System.NotSupportedException
ilist.Insert(0, 0); // throws System.NotSupportedException
ilist.Remove(3); // throws System.NotSupportedException
ilist.RemoveAt(0); // throws System.NotSupportedException
Tapi ternyata tidak. Jawabannya adalah bahwa contoh yang digunakan IList <T> / ICollection <T> salah. Jika Anda menggunakan ICollection <T> Anda perlu memeriksa flag IsReadOnly.
if (!ilist.IsReadOnly)
{
ilist.Add(4);
ilist.Insert(0, 0);
ilist.Remove(3);
ilist.RemoveAt(0);
}
else
{
// what were you planning to do if you were given a read only list anyway?
}
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 menggunakan IList<T>
/ ICollection<T>
dengan benar . Anda tidak dapat menjamin bahwa Anda dapat mengganti kode ke IList<T>
/ ICollection<T>
yang menggunakan List<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 tidak IEnumerable<T>
? Ini seringkali lebih cocok jika Anda tidak perlu menambahkan item. Jika Anda perlu menambah koleksi, gunakan jenis beton List<T>
,.
Bagi saya IList<T>
(dan ICollection<T>
) adalah bagian terburuk dari kerangka .NET. IsReadOnly
melanggar prinsip paling tidak mengejutkan. Kelas, seperti Array
, 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 menggunakan IList<T>
bukan List<T>
, meminta mereka bagaimana mereka akan menambahkan elemen ke IList<T>
. Jika mereka tidak tahuIsReadOnly
(dan kebanyakan orang tidak), maka jangan gunakan IList<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
IsReadOnly
adalah true
untuk IList
, Anda masih dapat memodifikasi anggota saat ini! Mungkin properti itu harus dinamai IsFixedSize
?
Array
sebagai IList
, jadi itu sebabnya saya bisa memodifikasi anggota saat ini)
IReadOnlyCollection<T>
dan IReadOnlyList<T>
yang hampir selalu lebih cocok daripada IList<T>
tetapi tidak memiliki semantik malas berbahaya IEnumerable<T>
. Dan mereka kovarian. Hindari IList<T>
!
List<T>
adalah implementasi spesifik IList<T>
, yang merupakan wadah yang dapat dialamatkan dengan cara yang sama seperti array linier T[]
menggunakan indeks integer. Ketika Anda menentukan IList<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 NET LinkedList<T>
tidak tidak melaksanakan IList<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 mengimplementasikan IList<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>
, yang IList<T>
meluas.
LinkedList<T>
vs mengambil List<T>
vs IList<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.
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>
class A : B, IList<T> { ... }
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.
public void Foo(IList<Bar> list)
{
// Do Something with the list here.
}
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.
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)
Reverse
dan ToArray
merupakan metode ekstensi untuk antarmuka <n> IEnumerable, yang berasal dari <isti> IList.
List
dan bukan implementasi lainnya IList
.
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.
Bagaimana jika NET 5.0 Menggantikan System.Collections.Generic.List<T>
ke System.Collection.Generics.LinearList<T>
. .NET selalu memiliki nama List<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
Semua konsep pada dasarnya dinyatakan dalam sebagian besar jawaban di atas mengenai mengapa menggunakan antarmuka dibandingkan implementasi konkret.
IList<T> defines those methods (not including extension methods)
IList<T>
Tautan MSDN
List<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 MSDN
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.
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
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.
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>
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.