Mengembalikan 'IList' vs 'ICollection' vs 'Collection'

119

Saya bingung tentang jenis koleksi mana yang harus saya kembalikan dari metode dan properti API publik saya.

Koleksi yang ada dalam pikiran saya adalah IList, ICollectiondan Collection.

Apakah mengembalikan salah satu jenis ini selalu lebih disukai daripada yang lain, atau apakah itu tergantung pada situasi tertentu?

Rocky Singh
sumber

Jawaban:

71

Secara umum, Anda harus mengembalikan tipe yang seumum mungkin, yaitu yang cukup mengetahui data yang dikembalikan yang perlu digunakan konsumen. Dengan begitu, Anda memiliki kebebasan yang lebih besar untuk mengubah implementasi API, tanpa merusak kode yang menggunakannya.

Pertimbangkan juga IEnumerable<T>antarmuka sebagai tipe kembali. Jika hasilnya hanya akan diulang, konsumen tidak membutuhkan lebih dari itu.

Guffa
sumber
25
Tetapi juga pertimbangkan ICollection <T> yang memperluas IEnumerable <T> untuk menyediakan utilitas tambahan termasuk properti Count. Ini dapat membantu karena Anda dapat menentukan panjang urutan tanpa melintasi urutan beberapa kali.
retrodrone
11
@retrodrone: Sebenarnya implementasi Countmetode ini memeriksa tipe aktual dari koleksi, jadi ia akan menggunakan properti Lengthor Countuntuk beberapa tipe yang dikenal seperti array dan ICollectionbahkan ketika Anda memasukkannya sebagai sebuah IEnumerable.
Guffa
8
@ Guffa: Perlu diperhatikan bahwa jika sebuah kelas Thing<T>mengimplementasikan IList<T>tetapi bukan yang non-generik ICollection, maka pemanggilan IEnumerable<Cat>.Count()a Thing<Cat>akan cepat, tetapi pemanggilan IEnumerable<Animal>.Count()akan lambat (karena metode ekstensi akan mencari, dan tidak menemukan, implementasi dari ICollection<Cat>). Jika kelas mengimplementasikan non-generik ICollection, bagaimanapun, IEnumerable<Animal>.Countakan menemukan dan menggunakan itu.
supercat
8
Saya tidak setuju. Yang terbaik adalah mengembalikan jenis terkaya yang Anda miliki sehingga klien dapat menggunakannya secara langsung jika itu yang mereka inginkan. Jika Anda memiliki List <>, mengapa mengembalikan IEnuerable <> hanya untuk memaksa pemanggil memanggil ToList () secara tidak perlu?
zumalifeguard
140

ICollection<T>adalah sebuah antarmuka yang mengekspos semantik koleksi seperti Add(), Remove(), dan Count.

Collection<T>adalah implementasi konkret dari ICollection<T>antarmuka.

IList<T>pada dasarnya adalah ICollection<T>dengan akses berbasis pesanan acak.

Dalam kasus ini, Anda harus memutuskan apakah hasil Anda memerlukan semantik daftar seperti pengindeksan berbasis urutan (lalu gunakan IList<T>) atau apakah Anda hanya perlu mengembalikan "kantong" hasil yang tidak berurutan (lalu gunakan ICollection<T>).

cordialgerm
sumber
5
Collection<T>mengimplementasikan IList<T>dan tidak hanya ICollection<T>.
CodesInChaos
56
Semua koleksi di .Net dipesan, karena IEnumerable<T>dipesan. Yang membedakan IList<T>dari ICollection<T>adalah bahwa ia menawarkan anggota untuk bekerja dengan indeks. Misalnya list[5]berfungsi, tetapi collection[5]tidak dapat dikompilasi.
svick
5
Saya juga tidak setuju bahwa koleksi yang dipesan harus diterapkan IList<T>. Ada banyak koleksi pesanan yang tidak. IList<T>adalah tentang akses cepat yang diindeks.
CodesInChaos
4
@yoyo Kelas dan antarmuka koleksi .net didesain dengan buruk dan dinamai dengan buruk. Saya tidak akan terlalu memikirkan mengapa mereka menamai mereka seperti itu.
CodesInChaos
6
@svick: Apakah semua koleksi dipesan karena IEnumerable<T>dipesan? Ambil HashSet<T>- itu mengimplementasikan IEnumerable<T>tetapi jelas tidak dipesan. Misalnya, Anda tidak memiliki pengaruh pada urutan item dan urutan ini dapat berubah kapan saja, jika tabel hash internal diatur ulang.
Olivier Jacot-Descombes
61

Perbedaan utama antara IList<T>and ICollection<T>is yang IList<T>memungkinkan Anda mengakses elemen melalui indeks. IList<T>mendeskripsikan tipe seperti array. Elemen dalam sebuah ICollection<T>hanya dapat diakses melalui pencacahan. Keduanya memungkinkan penyisipan dan penghapusan elemen.

Jika Anda hanya perlu menghitung koleksi, maka IEnumerable<T>lebih disukai. Ini memiliki dua keunggulan dibandingkan yang lain:

  1. Ini melarang perubahan pada koleksi (tetapi tidak pada elemen, jika mereka adalah tipe referensi).

  2. Ini memungkinkan variasi sumber terbesar, termasuk enumerasi yang dihasilkan secara algoritmik dan bukan merupakan kumpulan sama sekali.

Collection<T>adalah kelas dasar yang berguna terutama untuk pelaksana koleksi. Jika Anda mengeksposnya di antarmuka (API), banyak koleksi berguna yang tidak berasal darinya akan dikecualikan.


Salah satu kelemahannya IList<T>adalah bahwa array mengimplementasikannya tetapi tidak mengizinkan Anda untuk menambah atau menghapus item (misalnya, Anda tidak dapat mengubah panjang array). Pengecualian akan diberikan jika Anda memanggil IList<T>.Add(item)sebuah array. Situasi ini agak mereda karena IList<T>memiliki properti Boolean IsReadOnlyyang dapat Anda periksa sebelum mencoba melakukannya. Tapi di mata saya, ini masih cacat desain di perpustakaan . Oleh karena itu, saya menggunakan List<T>secara langsung, ketika kemungkinan untuk menambah atau menghapus item diperlukan.

Olivier Jacot-Descombes
sumber
Analisis kode (dan FxCop) menyarankan bahwa ketika kembali koleksi generik beton, salah satu berikut harus dikembalikan: Collection, ReadOnlyCollectionatau KeyedCollection. Dan itu Listtidak boleh dikembalikan.
DavidRR
@DavidRR: Seringkali berguna untuk meneruskan daftar ke metode yang harus menambah atau menghapus item. Jika Anda mengetik parameter sebagai IList<T>, tidak ada cara untuk memastikan bahwa metode tersebut tidak akan dipanggil dengan array sebagai parameter.
Olivier Jacot-Descombes
7

IList<T>adalah antarmuka dasar untuk semua daftar umum. Karena ini adalah koleksi yang dipesan, implementasi dapat memutuskan urutannya, mulai dari urutan yang diurutkan hingga urutan penyisipan. Selain itu, Ilistmemiliki properti Item yang memungkinkan metode untuk membaca dan mengedit entri dalam daftar berdasarkan indeksnya. Ini memungkinkan untuk menyisipkan, menghapus nilai ke / dari daftar pada indeks posisi.

Juga karena IList<T> : ICollection<T>, semua metode dari ICollection<T>juga tersedia di sini untuk diimplementasikan.

ICollection<T>adalah antarmuka dasar untuk semua koleksi umum. Ini mendefinisikan ukuran, enumerator dan metode sinkronisasi. Anda dapat menambah atau menghapus item ke dalam koleksi tetapi Anda tidak dapat memilih di posisi mana hal itu terjadi karena tidak adanya properti indeks.

Collection<T>menyediakan implementasi untuk IList<T>, IListdan IReadOnlyList<T>.

Jika Anda menggunakan jenis antarmuka yang lebih sempit seperti ICollection<T>daripada IList<T>, Anda melindungi kode Anda dari perubahan yang melanggar. Jika Anda menggunakan tipe antarmuka yang lebih luas seperti IList<T>, Anda lebih berisiko melanggar perubahan kode.

Mengutip dari sebuah sumber ,

ICollection, ICollection<T>: Anda ingin mengubah koleksi atau Anda peduli dengan ukurannya. IList, IList<T>: Anda ingin mengubah koleksi dan Anda peduli tentang urutan dan / atau posisi elemen dalam koleksi.

NullReference
sumber
5

Mengembalikan jenis antarmuka lebih umum, jadi (kurang informasi lebih lanjut tentang kasus penggunaan spesifik Anda) saya akan condong ke arah itu. Jika Anda ingin mengekspos dukungan pengindeksan, pilih IList<T>, jika tidak ICollection<T>akan cukup. Terakhir, jika Anda ingin menunjukkan bahwa tipe yang dikembalikan hanya baca, pilih IEnumerable<T>.

Dan, jika Anda belum membacanya sebelumnya, Brad Abrams dan Krzysztof Cwalina menulis buku hebat berjudul "Framework Design Guidelines: Conventions, Idiom, and Patterns for Reusable .NET Libraries" (Anda dapat mengunduh intisari dari sini ).

Alan
sumber
IEnumerable<T>apakah hanya baca? Tentu, tetapi ketika melakukan retuning dalam konteks repositori, bahkan setelah menjalankan ToList tidak aman untuk thread. Masalahnya adalah ketika sumber data asli mendapatkan GC maka IEnumarble.ToList () tidak berfungsi dalam konteks multi-utas. Ia bekerja pada satu linier karena GC dipanggil setelah Anda melakukan pekerjaan tetapi menggunakan asyncsekarang hari di 4.5+ IEnumarbale menjadi sakit bola.
Piotr Kula
-3

Ada beberapa hal yang muncul dari pertanyaan ini:

  • antarmuka versus kelas
  • kelas tertentu yang mana, dari beberapa kelas yang sama, koleksi, daftar, larik?
  • Kelas umum versus koleksi subitem ("generik")

Anda mungkin ingin menyoroti bahwa itu adalah API Berorientasi Objek

antarmuka versus kelas

Jika Anda tidak memiliki banyak pengalaman dengan antarmuka, saya sarankan tetap mengikuti kelas. Saya melihat banyak kali pengembang beralih ke antarmuka, meskipun itu tidak perlu.

Dan, akhiri dengan melakukan desain antarmuka yang buruk, alih-alih, desain kelas yang baik, yang pada akhirnya dapat bermigrasi ke desain antarmuka yang baik ...

Anda akan melihat banyak antarmuka di API, tetapi, jangan terburu-buru, jika Anda tidak membutuhkannya.

Anda akhirnya akan belajar bagaimana menerapkan antarmuka, ke kode Anda.

kelas tertentu yang mana, dari beberapa kelas yang sama, koleksi, daftar, larik?

Ada beberapa kelas di c # (dotnet) yang dapat dipertukarkan. Seperti yang telah disebutkan, jika Anda membutuhkan sesuatu dari kelas yang lebih spesifik, seperti "CanBeSortedClass", buatlah itu eksplisit di API Anda.

Apakah pengguna API Anda benar-benar perlu tahu, bahwa kelas Anda dapat diurutkan, atau menerapkan beberapa format ke elemen? Kemudian gunakan "CanBeSortedClass" atau "ElementsCanBePaintedClass", jika tidak gunakan "GenericBrandClass".

Jika tidak, gunakan kelas yang lebih umum.

Kelas koleksi umum versus koleksi subitem ("generik")

Anda akan menemukan bahwa ada kelas yang berisi elemen lain, dan Anda dapat menentukan bahwa semua elemen harus dari tipe tertentu.

Koleksi Generik adalah kelas-kelas yang Anda dapat menggunakan koleksi yang sama, untuk beberapa aplikasi kode, tanpa harus membuat koleksi baru, untuk setiap jenis subitem baru, seperti ini: Koleksi .

Apakah pengguna API Anda membutuhkan tipe yang sangat spesifik, sama untuk semua elemen?

Gunakan sesuatu seperti List<WashingtonApple>.

Apakah pengguna API Anda akan membutuhkan beberapa jenis terkait?

Ekspos List<Fruit>untuk API Anda, dan gunakan List<Orange> List<Banana>, secara List<Strawberry>internal, dari mana Orange, Bananadan Strawberrymerupakan turunannya Fruit.

Apakah pengguna API Anda akan membutuhkan koleksi tipe generik?

Gunakan List, di mana semua item berada object.

Bersulang.

umlcat
sumber
7
"Jika Anda tidak memiliki banyak pengalaman dengan antarmuka, saya sarankan tetap mengikuti kelas" Ini bukan nasihat yang baik. Itu seperti mengatakan jika ada sesuatu yang tidak Anda ketahui, menjauhlah darinya. Antarmuka sangat kuat dan mendukung konsep "Program abstraksi vs. konkresi" Mereka juga cukup kuat untuk melakukan pengujian unit karena kemampuan untuk bertukar jenis melalui tiruan. Ada banyak alasan bagus lainnya untuk menggunakan Antarmuka selain komentar-komentar ini, jadi harap jelajahi dengan segala cara.
atconway
@atconway Saya biasa menggunakan antarmuka, tetapi, karena banyak konsep, ini adalah alat yang ampuh, dapat dengan mudah digunakan campuran. Dan, terkadang, pengembang mungkin tidak membutuhkannya. Saran saya bukanlah "jangan pernah antarmuka", saran saya adalah "dalam hal ini, Anda dapat melewati antarmuka. Pelajari tentang hal itu, dan Anda dapat memperoleh yang terbaik dari mereka, nanti". Bersulang.
umlcat
1
Saya sarankan untuk selalu memprogram ke sebuah antarmuka. Ini membantu menjaga kode tetap terhubung secara longgar.
mac10688
@ mac10688 Jawaban saya sebelumnya (atconway) juga berlaku untuk komentar Anda.
umlcat