Tampaknya ada banyak cara untuk beralih pada koleksi. Ingin tahu apakah ada perbedaan, atau mengapa Anda menggunakan satu cara di atas yang lain.
Tipe pertama:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Cara lain:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Saya kira dari atas kepala saya, bahwa alih-alih delegasi anonim yang saya gunakan di atas, Anda akan memiliki delegasi yang dapat digunakan kembali yang dapat Anda tentukan ...
Jawaban:
Ada satu perbedaan penting dan berguna antara keduanya.
Karena .ForEach menggunakan
for
loop untuk mengulang koleksi, ini valid (sunting: sebelum .net 4.5 - implementasinya berubah dan mereka berdua melempar):sedangkan
foreach
menggunakan enumerator, jadi ini tidak valid:tl; dr: JANGAN menyalin kode ini ke dalam aplikasi Anda!
Contoh-contoh ini bukan praktik terbaik, mereka hanya untuk menunjukkan perbedaan antara
ForEach()
danforeach
.Menghapus item dari daftar dalam satu
for
lingkaran dapat memiliki efek samping. Yang paling umum dijelaskan dalam komentar untuk pertanyaan ini.Secara umum, jika Anda ingin menghapus beberapa item dari daftar, Anda ingin memisahkan penentuan item mana yang akan dihapus dari penghapusan yang sebenarnya. Itu tidak membuat kode Anda kompak, tetapi itu menjamin bahwa Anda tidak kehilangan barang apa pun.
sumber
Kami memiliki beberapa kode di sini (dalam VS2005 dan C # 2.0) di mana para insinyur sebelumnya menggunakan cara mereka
list.ForEach( delegate(item) { foo;});
alih-alihforeach(item in list) {foo; };
untuk semua kode yang mereka tulis. misalnya satu blok kode untuk membaca baris dari dataReader.Saya masih tidak tahu persis mengapa mereka melakukan ini.
Kelemahannya
list.ForEach()
adalah:Itu lebih verbose di C # 2.0. Namun, dalam C # 3 dan seterusnya, Anda dapat menggunakan
=>
sintaksis " " untuk membuat beberapa ekspresi yang tepat.Itu kurang akrab. Orang yang harus mempertahankan kode ini akan bertanya-tanya mengapa Anda melakukannya dengan cara itu. Butuh beberapa saat untuk memutuskan bahwa tidak ada alasan, kecuali mungkin untuk membuat penulis tampak pintar (kualitas sisa kode merusak itu). Itu juga kurang dapat dibaca, dengan "
})
" di akhir blok kode delegasi.Lihat juga buku Bill Wagner "Efektif C #: 50 Cara Khusus untuk Meningkatkan C # Anda" di mana ia berbicara tentang mengapa foreach lebih disukai daripada loop lain seperti untuk atau sementara loop - poin utamanya adalah bahwa Anda membiarkan kompiler memutuskan cara terbaik untuk membangun putaran. Jika versi kompiler di masa depan berhasil menggunakan teknik yang lebih cepat, maka Anda akan mendapatkannya secara gratis dengan menggunakan foreach dan membangun kembali, daripada mengubah kode Anda.
sebuah
foreach(item in list)
konstruk memungkinkan Anda untuk menggunakanbreak
ataucontinue
jika Anda harus keluar dari iterasi atau loop. Tetapi Anda tidak dapat mengubah daftar di dalam foreach loop.Saya terkejut melihat itu
list.ForEach
sedikit lebih cepat. Tapi itu mungkin bukan alasan yang valid untuk menggunakannya, itu akan menjadi optimasi prematur. Jika aplikasi Anda menggunakan database atau layanan web yang, bukan kontrol loop, hampir selalu akan berada di tempat waktu berlalu. Dan apakah Anda telah membandingkannya denganfor
loop juga? Thelist.ForEach
bisa lebih cepat karena menggunakan yang internal danfor
lingkaran tanpa pembungkus akan lebih cepat.Saya tidak setuju bahwa
list.ForEach(delegate)
versi "lebih fungsional" dengan cara apa pun yang signifikan. Itu melewati fungsi ke fungsi, tetapi tidak ada perbedaan besar dalam hasil atau program organisasi.Saya tidak berpikir bahwa
foreach(item in list)
"mengatakan dengan tepat bagaimana Anda menginginkannya dilakukan" - satufor(int 1 = 0; i < count; i++)
loop melakukan itu, satuforeach
loop meninggalkan pilihan kontrol hingga ke kompiler.Perasaan saya adalah, pada proyek baru, digunakan
foreach(item in list)
untuk sebagian besar loop untuk mematuhi penggunaan umum dan untuk keterbacaan, danlist.Foreach()
hanya digunakan untuk blok pendek, ketika Anda dapat melakukan sesuatu yang lebih elegan atau kompak dengan=>
operator C # 3 " ". Dalam kasus seperti itu, mungkin sudah ada metode ekstensi LINQ yang lebih spesifik daripadaForEach()
. Lihat apakahWhere()
,Select()
,Any()
,All()
,Max()
atau salah satu dari banyak metode LINQ lain tidak sudah melakukan apa yang Anda inginkan dari loop.sumber
Untuk bersenang-senang, saya memasukkan Daftar ke reflektor dan ini adalah hasil C #:
Demikian pula, MoveNext di Enumerator yang digunakan oleh foreach adalah sebagai berikut:
Daftar. FOREach jauh lebih dipangkas daripada MoveNext - pemrosesan jauh lebih sedikit - akan lebih mungkin JIT menjadi sesuatu yang efisien ..
Selain itu, foreach () akan mengalokasikan Enumerator baru apa pun yang terjadi. GC adalah teman Anda, tetapi jika Anda melakukan hal yang sama berulang kali, ini akan membuat lebih banyak objek yang dibuang, dibandingkan menggunakan kembali delegasi yang sama - TETAPI - ini benar-benar kasus pinggiran. Dalam penggunaan umum Anda akan melihat sedikit atau tidak ada perbedaan.
sumber
Saya tahu dua hal yang tidak jelas yang membuat mereka berbeda. Pergi aku!
Pertama, ada bug klasik membuat delegasi untuk setiap item dalam daftar. Jika Anda menggunakan kata kunci foreach, semua delegasi Anda dapat merujuk pada item terakhir dari daftar:
Metode List.ForEach tidak memiliki masalah ini. Item iterasi saat ini diteruskan oleh nilai sebagai argumen ke lambda luar, dan kemudian lambda dalam menangkap dengan benar argumen itu dalam penutupannya sendiri. Masalah terpecahkan.
(Sedihnya saya percaya ForEach adalah anggota List, daripada metode ekstensi, meskipun mudah untuk mendefinisikannya sendiri sehingga Anda memiliki fasilitas ini pada jenis enumerable apa pun.)
Kedua, pendekatan metode ForEach memiliki keterbatasan. Jika Anda menerapkan IEnumerable dengan menggunakan pengembalian hasil, Anda tidak bisa melakukan pengembalian hasil di dalam lambda. Jadi perulangan item dalam koleksi untuk menghasilkan hal-hal pengembalian tidak mungkin dengan metode ini. Anda harus menggunakan kata kunci foreach dan mengatasi masalah penutupan dengan secara manual membuat salinan nilai loop saat ini di dalam loop.
Lebih lanjut di sini
sumber
foreach
adalah "diperbaiki" di C # 5. stackoverflow.com/questions/8898925/…Saya kira
someList.ForEach()
panggilan bisa dengan mudah diparalelkan sedangkan yang normalforeach
tidak mudah dijalankan paralel. Anda dapat dengan mudah menjalankan beberapa delegasi yang berbeda pada core yang berbeda, yang tidak mudah dilakukan dengan normalforeach
.Hanya 2 sen saya
sumber
Seperti yang mereka katakan, iblis ada di detail ...
Perbedaan terbesar antara dua metode pencacahan koleksi adalah yang
foreach
membawa negara, sedangkanForEach(x => { })
tidak.Tetapi mari kita gali lebih dalam, karena ada beberapa hal yang harus Anda sadari yang dapat memengaruhi keputusan Anda, dan ada beberapa peringatan yang harus Anda waspadai ketika melakukan koding untuk kedua kasus.
Mari kita gunakan
List<T>
dalam percobaan kecil kami untuk mengamati perilaku. Untuk percobaan ini, saya menggunakan .NET 4.7.2:Mari kita beralih dari ini dengan yang
foreach
pertama:Kami dapat memperluas ini ke:
Dengan enumerator di tangan, mencari di bawah selimut kita dapatkan:
Dua hal menjadi bukti langsung:
Ini tentu saja tidak aman. Seperti yang ditunjukkan di atas, mengubah koleksi saat iterasi hanya mojo yang buruk.
Tetapi bagaimana dengan masalah koleksi yang menjadi tidak valid selama iterasi dengan cara di luar dari kita mucking dengan koleksi selama iterasi? Praktik terbaik menyarankan untuk memvariasikan koleksi selama operasi dan iterasi, dan memeriksa versi untuk mendeteksi kapan koleksi yang mendasarinya berubah.
Di sinilah segalanya menjadi sangat keruh. Menurut dokumentasi Microsoft:
Nah, apa artinya itu? Sebagai contoh, hanya karena
List<T>
mengimplementasikan penanganan pengecualian tidak berarti bahwa semua koleksi yang mengimplementasikanIList<T>
akan melakukan hal yang sama. Itu tampaknya merupakan pelanggaran yang jelas terhadap Prinsip Pergantian Liskov:Masalah lain adalah bahwa enumerator harus mengimplementasikan
IDisposable
- yang berarti sumber kebocoran memori potensial, tidak hanya jika penelepon salah, tetapi jika penulis tidak menerapkanDispose
pola dengan benar.Terakhir, kami memiliki masalah seumur hidup ... apa yang terjadi jika iterator valid, tetapi koleksi yang mendasarinya hilang? Kami sekarang snapshot dari apa yang ... ketika Anda memisahkan koleksi seumur hidup dan iteratornya, Anda meminta masalah.
Sekarang mari kita periksa
ForEach(x => { })
:Ini berkembang menjadi:
Yang penting diperhatikan adalah sebagai berikut:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Kode ini tidak mengalokasikan enumerator apa pun (tidak ada ke
Dispose
), dan tidak berhenti saat iterasi.Perhatikan bahwa ini juga melakukan salinan dangkal dari koleksi yang mendasarinya, tetapi koleksi sekarang menjadi snapshot dalam waktu. Jika penulis tidak benar menerapkan cek untuk koleksi yang berubah atau menjadi 'basi', foto itu masih valid.
Ini sama sekali tidak melindungi Anda dari masalah masalah seumur hidup ... jika koleksi yang mendasarinya hilang, Anda sekarang memiliki salinan dangkal yang menunjuk ke apa yang ... tetapi setidaknya Anda tidak memiliki
Dispose
masalah untuk berurusan dengan iterator yatim ...Ya, saya katakan iterators ... kadang-kadang menguntungkan untuk memiliki keadaan. Misalkan Anda ingin mempertahankan sesuatu yang mirip dengan kursor basis data ... mungkin beberapa
foreach
gayaIterator<T>
adalah cara untuk pergi. Saya pribadi tidak suka gaya desain ini karena ada terlalu banyak masalah seumur hidup, dan Anda mengandalkan rahmat baik dari penulis koleksi yang Anda andalkan (kecuali jika Anda benar-benar menulis semuanya sendiri dari awal).Selalu ada opsi ketiga ...
Ini tidak seksi, tetapi giginya tajam (permintaan maaf kepada Tom Cruise dan film The Firm )
Ini pilihan Anda, tetapi sekarang Anda tahu dan itu bisa menjadi pilihan.
sumber
Anda bisa memberi nama delegasi anonim :-)
Dan Anda dapat menulis yang kedua sebagai:
Yang saya sukai, dan menyimpan banyak pengetikan.
Seperti yang dikatakan Joachim, paralelisme lebih mudah diterapkan pada bentuk kedua.
sumber
Di belakang layar, delegasi anonim akan berubah menjadi metode aktual sehingga Anda dapat memiliki beberapa overhead dengan pilihan kedua jika kompiler tidak memilih untuk sebaris fungsi. Selain itu, variabel lokal apa pun yang dirujuk oleh badan contoh delegasi anonim akan berubah sifatnya karena trik kompiler untuk menyembunyikan fakta bahwa ia dikompilasi ke metode baru. Info lebih lanjut di sini tentang bagaimana C # melakukan sihir ini:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
sumber
Fungsi ForEach adalah anggota Daftar kelas generik.
Saya telah membuat ekstensi berikut untuk mereproduksi kode internal:
Jadi akhirnya kita menggunakan foreach normal (atau loop jika Anda mau).
Di sisi lain, menggunakan fungsi delegasi hanyalah cara lain untuk mendefinisikan suatu fungsi, kode ini:
setara dengan:
atau menggunakan ekspresi labda:
sumber
Seluruh lingkup ForEach (fungsi delegasi) diperlakukan sebagai satu baris kode (memanggil fungsi), dan Anda tidak dapat mengatur breakpoint atau masuk ke dalam kode. Jika pengecualian terjadi, seluruh blok ditandai.
sumber
List.ForEach () dianggap lebih fungsional.
List.ForEach()
mengatakan apa yang ingin kamu lakukan.foreach(item in list)
juga mengatakan persis bagaimana Anda ingin itu dilakukan. IniList.ForEach
bebas untuk mengubah implementasi bagian bagaimana di masa depan. Misalnya, versi .Net hipotetis di masa depan mungkin selalu berjalanList.ForEach
secara paralel, dengan asumsi bahwa pada titik ini setiap orang memiliki sejumlah inti cpu yang umumnya tidak digunakan.Di sisi lain,
foreach (item in list)
memberi Anda sedikit lebih banyak kontrol atas loop. Misalnya, Anda tahu bahwa item akan diulang dalam beberapa jenis urutan berurutan, dan Anda bisa dengan mudah pecah di tengah jika suatu item memenuhi beberapa kondisi.Beberapa komentar terbaru tentang masalah ini tersedia di sini:
sumber
Cara kedua yang Anda tunjukkan menggunakan metode ekstensi untuk menjalankan metode delegasi untuk setiap elemen dalam daftar.
Dengan cara ini, Anda memiliki panggilan delegasi (= metode) lain.
Selain itu, ada kemungkinan untuk mengulang daftar dengan loop for .
sumber
Satu hal yang perlu diwaspadai adalah bagaimana keluar dari metode .ForEach Generik - lihat diskusi ini . Meskipun tautan sepertinya mengatakan bahwa cara ini adalah yang tercepat. Tidak yakin mengapa - Anda akan berpikir mereka akan setara setelah dikompilasi ...
sumber
Ada cara, saya telah melakukannya di aplikasi saya:
Anda dapat menggunakan sebagai item Anda dari foreach
sumber