Apakah saya harus selalu mengembalikan IEnumerable <T> daripada IList <T>?

97

Saat saya menulis DAL saya atau kode lain yang mengembalikan sekumpulan item, haruskah saya selalu membuat pernyataan pengembalian:

public IEnumerable<FooBar> GetRecentItems()

atau

public IList<FooBar> GetRecentItems()

Saat ini, dalam kode saya, saya telah mencoba menggunakan IEnumerable sebanyak mungkin tetapi saya tidak yakin apakah ini adalah praktik terbaik? Sepertinya benar karena saya mengembalikan tipe data paling umum sambil tetap mendeskripsikan apa yang dilakukannya, tetapi mungkin ini tidak benar untuk dilakukan.

KingNestor
sumber
1
Apakah List <T> atau IList <T> yang Anda tanyakan? Judul dan pertanyaan mengatakan hal yang berbeda ...
Fredrik Mörk
Jika memungkinkan, antarmuka pengguna IEnumerable atau Ilist sebagai pengganti tipe concerete.
Usman Masood
2
Saya mengembalikan koleksi sebagai Daftar <T>. Saya tidak melihat perlunya mengembalikan IEnumberable <T> karena Anda dapat mengekstraknya dari List <T>
Chuck Conway
kemungkinan duplikat ienumerablet-as-return-type
nawfal

Jawaban:

44

Itu benar-benar tergantung pada mengapa Anda menggunakan antarmuka khusus itu.

Misalnya, IList<T>memiliki beberapa metode yang tidak ada di IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

dan Properti:

  • T this[int index] { get; set; }

Jika Anda membutuhkan metode ini dengan cara apa pun, silakan kembali IList<T>.

Selain itu, jika metode yang menggunakan IEnumerable<T>hasil Anda mengharapkan IList<T>, itu akan menghemat CLR dari mempertimbangkan setiap konversi yang diperlukan, sehingga mengoptimalkan kode yang dikompilasi.

Jon Limjap
sumber
2
@Jon FDG merekomendasikan menggunakan Collection <T> atau ReadOnlyCollection <T> sebagai nilai pengembalian untuk tipe koleksi, lihat jawaban saya.
Sam Saffron
Anda tidak jelas apa yang Anda maksud di kalimat terakhir saat Anda mengatakan "itu akan menyelamatkan CLR". Apa yang akan menyimpannya, menggunakan IEnumerable vs. IList? Bisakah Anda membuatnya lebih jelas?
PositiveGuy
1
@CoffeeAddict Tiga tahun setelah jawaban ini saya pikir Anda benar - bagian terakhir itu tidak jelas. Jika metode yang mengharapkan IList <T> sebagai parameter mendapatkan IEnumerable <T> IEnumerable harus dibungkus secara manual dalam List <T> baru atau implementor IList <T> lainnya, dan pekerjaan itu tidak akan dilakukan oleh CLR untuk Anda. Kebalikannya - metode yang mengharapkan IEnumerable <T> mendapatkan IList <T>, mungkin harus melakukan beberapa unboxing tetapi setelah melihat ke belakang mungkin tidak perlu karena IList <T> mengimplementasikan IEnumerable <T>.
Jon Limjap
68

Panduan desain kerangka merekomendasikan penggunaan class Collection saat Anda perlu mengembalikan koleksi yang dapat dimodifikasi oleh pemanggil atau ReadOnlyCollection untuk koleksi hanya baca.

Alasan ini lebih disukai daripada yang sederhana IListadalah karena IListtidak memberi tahu penelepon apakah itu hanya baca atau tidak.

Jika Anda mengembalikan IEnumerable<T>, operasi tertentu mungkin sedikit lebih rumit untuk dilakukan oleh pemanggil. Anda juga tidak akan lagi memberi penelepon fleksibilitas untuk mengubah koleksinya, sesuatu yang mungkin Anda inginkan atau tidak inginkan.

Perlu diingat bahwa LINQ berisi beberapa trik dan akan mengoptimalkan panggilan tertentu berdasarkan jenisnya. Jadi, misalnya, jika Anda menjalankan a Countdan koleksi yang mendasarinya adalah List, ini TIDAK akan berjalan melalui semua elemen.

Secara pribadi, untuk ORM saya mungkin akan tetap menggunakannya Collection<T>sebagai nilai pengembalian saya.

Sam Saffron
sumber
12
The Pedoman Koleksi berisi daftar yang lebih rinci dari Dos and DONTs.
Chaquotay
26

Secara umum, Anda harus meminta yang paling umum dan mengembalikan hal paling spesifik yang Anda bisa. Jadi jika Anda memiliki metode yang mengambil parameter, dan Anda hanya benar-benar membutuhkan apa yang tersedia di IEnumerable, maka itu harus menjadi jenis parameter Anda. Jika metode Anda dapat menampilkan IList atau IEnumerable, lebih baik menampilkan IList. Ini memastikan bahwa ini dapat digunakan oleh konsumen yang paling luas.

Bebaskan apa yang Anda butuhkan, dan jelaskan apa yang Anda berikan.

Mel
sumber
1
Saya sampai pada kesimpulan yang berlawanan: bahwa seseorang harus menerima tipe tertentu, dan mengembalikan tipe umum. Namun Anda mungkin benar bahwa metode yang dapat diterapkan secara lebih luas lebih baik daripada metode yang lebih terbatas. Saya harus memikirkan ini lebih lanjut.
Dave Cousineau
3
Alasan untuk menerima tipe umum sebagai input adalah bahwa hal itu memungkinkan Anda untuk bekerja dengan input seluas mungkin untuk mendapatkan penggunaan kembali komponen sebanyak mungkin. Di sisi lain, karena Anda sudah tahu persis objek apa yang Anda miliki, tidak ada gunanya menutupinya.
Mel
1
Saya rasa saya setuju dengan memiliki parameter yang lebih umum, tetapi apa alasan Anda mengembalikan sesuatu yang kurang umum?
Dave Cousineau
6
Oke, izinkan saya mencoba cara lain ini. Mengapa Anda membuang informasi? Jika Anda hanya peduli bahwa hasilnya adalah IEnumerable <T>, apakah ada salahnya Anda mengetahui bahwa ini adalah IList <T>? Tidak, tidak. Mungkin informasi yang tidak berguna dalam beberapa kasus, tetapi tidak membahayakan Anda. Sekarang untuk kepentingannya. Jika Anda mengembalikan List atau IList, saya dapat segera mengetahui bahwa koleksi tersebut telah diambil, sesuatu yang tidak dapat saya ketahui dengan IEnumerable. Ini mungkin informasi yang berguna atau mungkin tidak tetapi sekali lagi, mengapa Anda membuang informasi? Jika Anda mengetahui informasi tambahan tentang sesuatu, sebarkan.
Mel
Dalam retrospeksi, saya terkejut tidak ada yang pernah menelepon saya tentang itu. IEnumerable WOULD telah diambil. Namun, IQuer dapat dikembalikan dalam keadaan tidak terhitung. Jadi mungkin contoh yang lebih baik adalah mengembalikan IQuerizable vs IEnumerable. Mengembalikan IQuerizable yang lebih spesifik akan memungkinkan komposisi lebih lanjut, sedangkan mengembalikan IEnumerable akan memaksa pencacahan segera, dan menurunkan komposabilitas metode.
Mel
23

Itu tergantung...

Mengembalikan type yang paling sedikit diturunkan ( IEnumerable) akan memberi Anda kelonggaran paling besar untuk mengubah implementasi yang mendasari di jalurnya.

Mengembalikan type yang lebih diturunkan ( IList) memberi pengguna API Anda lebih banyak operasi pada hasilnya.

Saya selalu menyarankan untuk mengembalikan jenis yang paling tidak diturunkan yang memiliki semua operasi yang dibutuhkan pengguna Anda ... jadi pada dasarnya, Anda harus terlebih dahulu membatalkan operasi apa pada hasil yang masuk akal dalam konteks API yang Anda tentukan.

jerryjvl.dll
sumber
jawaban umum yang bagus karena berlaku untuk metode lain juga.
user420667
1
Saya tahu jawaban ini sangat tua ... tetapi tampaknya bertentangan dengan dokumentasi: "Jika baik antarmuka IDictionary <TKey, TValue> maupun IList <T> tidak memenuhi persyaratan koleksi yang diperlukan, dapatkan kelas koleksi baru dari antarmuka ICollection <T> sebagai gantinya untuk lebih banyak fleksibilitas "Ini tampaknya menyiratkan bahwa tipe yang lebih diturunkan harus lebih disukai. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK
11

Satu hal yang perlu dipertimbangkan adalah jika Anda menggunakan pernyataan LINQ eksekusi-ditangguhkan untuk menghasilkan Anda IEnumerable<T>, memanggil .ToList()sebelum Anda kembali dari metode Anda berarti bahwa item Anda mungkin diulang dua kali - sekali untuk membuat Daftar, dan sekali ketika pemanggil mengulang melalui , memfilter, atau mengubah nilai pengembalian Anda. Ketika praktis, saya suka menghindari mengubah hasil LINQ-to-Objects menjadi Daftar atau Kamus konkret sampai saya harus melakukannya. Jika pemanggil saya membutuhkan List, itu adalah satu metode yang mudah untuk ditelepon - saya tidak perlu membuat keputusan itu untuk mereka, dan itu membuat kode saya sedikit lebih efisien dalam kasus di mana pemanggil hanya melakukan foreach.

Joel Mueller
sumber
@ Joel Mueller, saya biasanya akan memanggil ToList () pada mereka. Saya biasanya tidak suka mengekspos IQuerizable ke sisa proyek saya.
KingNestor
4
Saya lebih mengacu pada LINQ-to-Objects, di mana IQueryable umumnya tidak masuk ke dalam gambar. Saat database terlibat, ToList () menjadi lebih diperlukan, karena jika tidak, Anda berisiko menutup koneksi sebelum melakukan iterasi, yang tidak berfungsi dengan baik. Namun, jika itu bukan masalah, cukup mudah untuk mengekspos IQuerizable sebagai IEnumerable tanpa memaksakan iterasi tambahan saat Anda ingin menyembunyikan IQuerable.
Joel Mueller
9

List<T>menawarkan kode panggilan lebih banyak fitur, seperti memodifikasi objek yang dikembalikan dan akses dengan indeks. Jadi, pertanyaannya bermuara pada: dalam kasus penggunaan khusus aplikasi Anda, apakah Anda INGIN mendukung penggunaan tersebut (mungkin dengan mengembalikan koleksi yang baru dibuat!), Untuk kenyamanan pemanggil - atau apakah Anda menginginkan kecepatan untuk kasus sederhana ketika semua kebutuhan pemanggil adalah untuk mengulang melalui koleksi dan Anda dapat dengan aman mengembalikan referensi ke koleksi nyata yang mendasarinya tanpa takut ini akan membuatnya salah diubah, dll?

Hanya Anda yang dapat menjawab pertanyaan ini, dan hanya dengan memahami dengan baik apa yang ingin dilakukan penelepon Anda dengan nilai pengembalian, dan seberapa penting kinerja di sini (seberapa besar koleksi yang akan Anda salin, seberapa besar kemungkinan ini menjadi hambatan, dll).

Alex Martelli
sumber
"mengembalikan referensi dengan aman ke koleksi asli yang sebenarnya tanpa takut ini akan mengubahnya secara keliru" - Meskipun Anda mengembalikan IEnumerable <T>, tidak bisakah mereka mengembalikannya ke Daftar <T> dan mengubahnya?
Kobi
Tidak setiap IEnumarable <T> juga merupakan List <T>. Jika objek yang dikembalikan bukan tipe yang mewarisi dari List <T> atau mengimplementasikan IList <T>, ini akan menghasilkan InvalidCastException.
lowglider
2
List <T> memiliki masalah yang membuat Anda terkunci pada implementasi tertentu. Koleksi <T> atau ReadOnlyCollection <T> lebih disukai
Sam Saffron
4

Saya pikir Anda dapat menggunakan keduanya, tetapi masing-masing memiliki kegunaan. Pada dasarnya Listadalah IEnumerabletetapi Anda memiliki fungsionalitas menghitung, menambah elemen, menghapus elemen

IEnumerable tidak efisien untuk menghitung elemen

Jika koleksi dimaksudkan untuk menjadi hanya-baca, atau modifikasi koleksi dikendalikan oleh Parentpengembalian yang IListadil Countbukanlah ide yang baik.

Di Linq, ada Count()metode ekstensi IEnumerable<T>yang di dalam CLR akan pintas ke .Countjika jenis yang mendasarinya adalah IList, sehingga perbedaan kinerja dapat diabaikan.

Secara umum saya merasa (pendapat) adalah praktik yang lebih baik untuk mengembalikan IEnumerable jika memungkinkan, jika Anda perlu melakukan penambahan maka tambahkan metode ini ke kelas induk, jika tidak, konsumen kemudian mengelola koleksi dalam Model yang melanggar prinsip, misalnya manufacturer.Models.Add(model)melanggar hukum demeter. Tentu saja ini hanyalah pedoman dan bukan aturan yang keras dan cepat, tetapi sampai Anda benar-benar memahami penerapannya, mengikuti secara membabi buta lebih baik daripada tidak mengikuti sama sekali.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Catatan: Jika menggunakan nNHibernate, Anda mungkin perlu memetakan ke IList pribadi menggunakan pengakses berbeda.)

Pengguna SO
sumber
2

Tidak sesederhana itu ketika Anda berbicara tentang nilai kembalian alih-alih parameter input. Jika itu adalah parameter masukan, Anda tahu persis apa yang perlu Anda lakukan. Jadi, jika Anda perlu untuk dapat mengulang koleksi, Anda mengambil IEnumberable sedangkan jika Anda perlu menambah atau menghapus, Anda menggunakan IList.

Dalam kasus nilai kembali, itu lebih sulit. Apa yang diharapkan penelepon Anda? Jika Anda mengembalikan IEnumerable, maka dia tidak akan tahu secara apriori bahwa dia dapat membuat IList darinya. Namun, jika Anda mengembalikan IList, dia akan tahu bahwa dia dapat mengulanginya. Jadi, Anda harus memperhitungkan apa yang akan dilakukan penelepon Anda dengan datanya. Fungsionalitas yang dibutuhkan / diharapkan pemanggil Anda adalah apa yang harus mengatur saat membuat keputusan tentang apa yang akan dikembalikan.

JP Alioto
sumber
0

karena semua telah mengatakan itu tergantung, jika Anda tidak ingin Tambah / Hapus fungsi pada lapisan panggilan maka saya akan memilih IEnumerable karena hanya menyediakan iterasi dan fungsionalitas dasar yang dalam perspektif desain saya suka. Kembali ke IList, pilihan saya selalu lagi, tetapi itu terutama apa yang Anda suka dan apa yang tidak. dalam hal kinerja saya pikir mereka lebih mirip.

Usman Masood
sumber
0

Jika Anda tidak menghitung dalam kode eksternal Anda, selalu lebih baik untuk mengembalikan IEnumerable, karena nanti Anda dapat mengubah implementasi Anda (tanpa dampak kode eksternal), misalnya, untuk logika iterator hasil dan menghemat sumber daya memori (fitur bahasa yang sangat baik dengan cara ).

Namun jika Anda membutuhkan item count, jangan lupa bahwa ada layer lain antara IEnumerable dan IList - ICollection .

wasit
sumber
0

Saya mungkin sedikit kesal di sini, melihat bahwa sejauh ini tidak ada orang lain yang menyarankannya, tetapi mengapa Anda tidak mengembalikannya (I)Collection<T>?

Dari apa yang saya ingat, Collection<T>adalah tipe pengembalian yang lebih disukai List<T>karena itu mengabstraksikan implementasinya. Mereka semua menerapkan IEnumerable, tetapi bagi saya itu terdengar terlalu rendah untuk pekerjaan itu.

Tiberiu Ana
sumber
0

Saya pikir Anda dapat menggunakan keduanya, tetapi masing-masing memiliki kegunaan. Pada dasarnya Listadalah IEnumerabletetapi Anda memiliki fungsionalitas hitungan, Tambahkan elemen, hapus elemen

IEnumerable tidak efisien untuk menghitung elemen, atau mendapatkan elemen tertentu dalam koleksi.

List adalah koleksi yang cocok untuk menemukan elemen tertentu, mudah untuk menambahkan elemen, atau menghapusnya.

Umumnya saya mencoba menggunakan Listjika memungkinkan karena ini memberi saya lebih banyak fleksibilitas.

Gunakan List<FooBar> getRecentItems() daripada IList<FooBar> GetRecentItems()

Stuart
sumber
0

Saya pikir aturan umumnya adalah menggunakan kelas yang lebih spesifik untuk kembali, untuk menghindari melakukan pekerjaan yang tidak diperlukan dan memberikan lebih banyak opsi kepada pemanggil Anda.

Meskipun demikian, menurut saya lebih penting untuk mempertimbangkan kode di depan Anda yang sedang Anda tulis daripada kode yang akan ditulis oleh orang berikutnya (sesuai alasannya). Ini karena Anda dapat membuat asumsi tentang kode yang sudah ada.

Ingat bahwa memindahkan NAIK ke koleksi dari IEnumerable dalam sebuah antarmuka akan berfungsi, pindah ke IEnumerable dari koleksi akan merusak kode yang ada.

Jika semua pendapat ini tampak bertentangan, itu karena keputusannya subjektif.

Sprague
sumber
0

TL; DR; - ringkasan

  • Jika Anda mengembangkan perangkat lunak internal, gunakan jenis tertentu (Suka List) untuk nilai yang dikembalikan dan jenis yang paling umum untuk parameter masukan bahkan dalam kasus pengumpulan.
  • Jika suatu metode adalah bagian dari API publik pustaka yang dapat didistribusikan ulang, gunakan antarmuka alih-alih tipe kumpulan konkret untuk memperkenalkan nilai kembalian dan parameter masukan.
  • Jika metode mengembalikan koleksi hanya-baca, perlihatkan itu dengan menggunakan IReadOnlyListatau IReadOnlyCollectionsebagai tipe nilai yang dikembalikan.

Lebih

Ali Bayat
sumber