Mengapa saya harus menggunakan Daftar <T> di atas IEnumerable <T>?

24

Dalam aplikasi web ASP.net MVC4 saya menggunakan IEnumerables, mencoba mengikuti mantra untuk memprogram ke antarmuka, bukan implementasi.

Return IEnumerable(Of Student)

vs.

Return New List(Of Student)

Orang-orang mengatakan kepada saya untuk menggunakan Daftar dan bukan IEnumerable, karena daftar memaksa kueri untuk dieksekusi dan IEumerable tidak.

Apakah ini benar-benar praktik terbaik? Apakah ada alternatif lain? Saya merasa aneh menggunakan benda konkret di mana antarmuka dapat digunakan. Apakah perasaan aneh saya dibenarkan?

Rowan Freeman
sumber
2
Pertama, mengapa hal itu baik untuk memaksa permintaan dieksekusi? Kedua, Anda harus memantau dan membuat profil panggilan basis data untuk mengevaluasi apakah pertimbangan teknis ini memiliki manfaat.
user16764
2
Dikatakan bahwa semua pertanyaan harus dieksekusi sehingga model dilakukan dan dimuat siap untuk dilihat. Yaitu tampilan harus menerima segalanya dan tidak meminta database.
Rowan Freeman
3
Ini pertanyaan StackOverflow mencakup dengan cukup baik.
Karl Bielefeldt
Itu jawaban yang bagus, tapi saya ingin tahu relevansinya dengan MVC. Mengapa tampilan tidak dapat diberikan IEnumerables sehingga semua kueri berjalan tepat waktu?
Rowan Freeman
2
"Dikatakan bahwa semua permintaan harus dieksekusi sehingga model dilakukan dan dimuat siap untuk tampilan. Yaitu tampilan harus menerima segalanya dan tidak akan meminta database." Itu tidak masuk akal. Jika Anda melewatkan IEnumerable, maka tampilan Anda tidak tahu atau peduli apakah itu menanyakan database. Dan memang seharusnya begitu.
user16764

Jawaban:

21

Ada kalanya melakukan ToList()permintaan pada linq Anda bisa menjadi penting untuk memastikan permintaan Anda dieksekusi pada saat itu dan dalam urutan yang Anda harapkan. Namun skenario itu jarang terjadi dan tidak ada yang harus dikhawatirkan terlalu banyak sampai mereka benar-benar bertemu dengannya.

Singkatnya, gunakan IEnumerablekapan saja Anda hanya perlu iterasi, gunakan IListsaat Anda perlu mengindeks secara langsung dan membutuhkan array berukuran dinamis (jika Anda perlu mengindeks pada array ukuran tetap maka gunakan saja array standar).

Adapun hal waktu eksekusi, Anda selalu dapat menggunakan daftar sebagai IEnumerablevariabel, jadi jangan ragu untuk mengembalikan sebuah IEnumerabledengan melakukan .ToList();, atau mengirimkan parameter sebagai IEnumerabledengan mengeksekusi .ToList()pada IEnumerableuntuk memaksa eksekusi saat itu juga. Berhati-hatilah bahwa kapan saja Anda memaksa eksekusi dengan .ToList()Anda, jangan bergantung pada IEnumerablevariabel yang baru saja Anda lakukan dan jalankan lagi, atau Anda akan berakhir dua kali lipat iterasi dalam permintaan LINQ Anda tidak perlu.

Berkenaan dengan MVC, tidak ada yang istimewa untuk dicatat di sini. Ini akan mengikuti aturan waktu eksekusi yang sama dengan .NET, saya pikir Anda mungkin memiliki seseorang yang sedikit bingung oleh penundaan eksekusi semantik di masa lalu dan menyalahkannya pada MVC yang memberitahu Anda ini entah bagaimana terkait, tapi itu tidak. Semantik eksekusi yang tertunda membingungkan semua orang pada awalnya (dan bahkan untuk sementara waktu sesudahnya; mereka bisa menjadi sentuhan yang rumit). Sekali lagi, jangan khawatir tentang hal itu sampai Anda benar-benar peduli untuk memastikan permintaan LINQ tidak dieksekusi dua kali atau mengharuskannya dieksekusi dalam urutan tertentu relatif terhadap kode lain, di mana titik menetapkan variabel Anda untuk dirinya sendiri. Daftar () untuk memaksa eksekusi dan Anda akan baik-baik saja.

Jimmy Hoffa
sumber
Apakah buruk untuk memberikan tampilan IEnumerables? Haruskah Anda memberikannya Daftar sehingga permintaan telah dieksekusi pada saat tampilan mendapatkannya?
Rowan Freeman
@RowanFreeman Saya baru saja menambahkan suntingan untuk menjawab ini. Saya pikir Anda memiliki seseorang yang mengalami sesuatu yang tidak sepenuhnya mereka pahami (tidak dapat menyalahkan mereka, penundaan eksekusi sangat rumit dan membingungkan) dan menghubungkannya dengan joojoo yang buruk alih-alih bekerja untuk memahami perilaku lengkap dari eksekusi yang tertunda semantik.
Jimmy Hoffa
Jawaban bagus. Jadi apakah saya benar-benar harus menggunakan .ToList ()? Sejauh ini aplikasi saya berfungsi dengan baik hanya menggunakan IEnumerables dan meneruskannya dari model ke tampilan. Tidak Ada Daftar atau .ToList (). Pertanyaan saya bukan fungsionalitas - Saya tahu aplikasi saya berfungsi. Pertanyaan saya adalah salah satu praktik terbaik.
Rowan Freeman
4
@RowanFreeman praktik terbaik adalah dengan menggunakan antarmuka minimal yang masih memenuhi persyaratan Anda. IEnumerable sedang melakukan ini sekarang untuk Anda, jadi jangan khawatir mengubahnya. Yang mengatakan, suatu hari akan datang ketika Anda akan memiliki permintaan mengeksekusi 3 atau 10 kali dan tidak mengerti mengapa, atau mengharapkan permintaan untuk mengeksekusi sebelum memasukkan hanya untuk menemukannya dieksekusi setelah itu, ini adalah saat-saat Anda perlu mengenali. Daftar () akan memaksa eksekusi ketika Anda inginkan, dan mengulangi IEnumerable dari kueri LINQ beberapa kali akan mengeksekusi seluruh kueri beberapa kali; Perbaiki eksekusi Anda saat peristiwa itu terjadi
Jimmy Hoffa
1
Anda bahkan tidak boleh menggunakan - Listmelewati sekitar Listmenyiratkan bahwa isi daftar akan diubah. Jika Anda ingin mengembalikan koleksi, gunakan IReadOnlyCollection. Listuntuk digunakan di dalam metode, dan untuk pertukaran di antara metode yang mengubah daftar. Itu dia!
ErikE
7

Ada dua masalah.

IENumerable<Data> query = MyQuery();

//Later
foreach (Data item in query) {
  //Process data
}

Saat loop "Data Proses" tercapai, kueri mungkin tidak lagi valid. Misalnya, jika kueri dijalankan pada DataContext yang sudah dibuang, kode Anda akan mengeluarkan pengecualian. Hal semacam ini menjadi sangat membingungkan ketika Anda memproses kueri dalam konteks yang berbeda dari tempat Anda membuatnya.

Masalah kedua adalah bahwa koneksi Anda tidak akan dirilis sampai loop "Data Proses" selesai. Ini hanya masalah jika "Data Proses" kompleks. Ini disebutkan di http://msdn.microsoft.com/en-us/library/bb386929.aspx :

Q. Berapa lama koneksi database saya tetap terbuka?

A. Koneksi biasanya tetap terbuka sampai Anda mengonsumsi hasil kueri. Jika Anda berharap untuk mengambil waktu untuk memproses semua hasil dan tidak menentang caching hasil, terapkan ToList ke kueri. Dalam skenario umum di mana setiap objek diproses hanya satu kali, model streaming lebih unggul di DataReader dan LINQ ke SQL.

Jadi, masalah ini adalah alasan mengapa Anda didorong untuk memastikan bahwa kueri benar-benar dieksekusi, misalnya dengan menelepon ToList(). Namun, seperti yang disarankan oleh Jimmy, tidak ada yang menghentikan Anda mengembalikan Daftar sebagai IEnumerable.

Sebagai aturan umum, saya sarankan menghindari iterasi lebih dari satu IEnumerable lebih dari sekali. Dengan asumsi konsumen kode Anda mengikuti aturan ini, saya tidak menganggapnya sebagai kekhawatiran bahwa seseorang mungkin menekan database dua kali dengan mengeksekusi kueri dua kali.

Brian
sumber
1

Manfaat lain dari penghitungan IEnumerableawal adalah pengecualian akan dilemparkan ke lokasi yang sesuai. Ini membantu debugging.

Misalnya, jika Anda mendapatkan pengecualian kebuntuan di dalam salah satu tampilan Razor Anda, itu tidak akan benar-benar sejelas jika pengecualian terjadi selama salah satu metode akses data Anda.

Sam
sumber
Metode apa pun yang mengembalikan IEnumerablelemparan yang mungkin membuat kesalahan. IEnumerableMetode yang ditangguhkan harus dibagi menjadi dua: satu metode yang tidak ditangguhkan memeriksa parameter dan mengatur, melempar jika perlu (katakanlah, karena argumen nol). Lalu ia mengembalikan panggilan untuk pelaksanaan swasta yang sedang ditangguhkan. Saya tidak berpikir komentar saya sepenuhnya bertentangan dengan jawaban Anda, tetapi jangan berpikir bahwa Anda telah meninggalkan aspek penting dalam jawaban Anda, yang merupakan makna semantik dari menggunakan IEnumerablevs. a List(mutasi) vs IReadOnlyCollection(tidak ada manfaatnya untuk penangguhan) .
ErikE