Ketika saya menulis kode dalam Visual Studio, ReSharper (Tuhan memberkati itu!) Sering menyarankan saya untuk mengubah sekolah lama saya untuk loop dalam bentuk foreach yang lebih kompak.
Dan sering, ketika saya menerima perubahan ini, ReSharper melangkah maju, dan menyarankan saya untuk mengubahnya lagi, dalam bentuk LINQ yang mengkilap.
Jadi, saya bertanya-tanya: apakah ada beberapa keuntungan nyata , dalam perbaikan ini? Dalam eksekusi kode yang cukup sederhana, saya tidak dapat melihat peningkatan kecepatan (jelas), tetapi saya dapat melihat kode menjadi semakin kurang dapat dibaca ... Jadi saya bertanya-tanya: apakah itu layak?
foreach
adalah menghapus item dari koleksi saat menghitungnya, di mana biasanya diperlukanfor
loop untuk memulai dari elemen terakhir.Jawaban:
for
vs.foreach
Ada kebingungan umum bahwa kedua konstruksi ini sangat mirip dan keduanya dapat dipertukarkan seperti ini:
dan:
Fakta bahwa kedua kata kunci dimulai dengan tiga huruf yang sama tidak berarti secara semantik, keduanya serupa. Kebingungan ini sangat rawan kesalahan, terutama untuk pemula. Iterasi melalui koleksi dan melakukan sesuatu dengan elemen dilakukan dengan
foreach
;for
tidak harus dan tidak boleh digunakan untuk tujuan ini , kecuali jika Anda benar-benar tahu apa yang Anda lakukan.Mari kita lihat apa yang salah dengan contoh itu. Pada akhirnya, Anda akan menemukan kode lengkap aplikasi demo yang digunakan untuk mengumpulkan hasil.
Dalam contoh, kami memuat beberapa data dari basis data, lebih tepatnya kota-kota dari Adventure Works, yang dipesan dengan nama, sebelum bertemu "Boston". Query SQL berikut digunakan:
Data dimuat oleh
ListCities()
metode yang mengembalikan sebuahIEnumerable<string>
. Berikut iniforeach
tampilannya:Mari kita menulis ulang dengan a
for
, dengan asumsi bahwa keduanya dapat dipertukarkan:Keduanya mengembalikan kota yang sama, tetapi ada perbedaan besar.
foreach
,ListCities()
disebut satu kali dan menghasilkan 47 item.for
,ListCities()
disebut 94 kali dan menghasilkan 28153 item secara keseluruhan.Apa yang terjadi?
IEnumerable
adalah malas . Ini berarti bahwa itu akan melakukan pekerjaan hanya pada saat ketika hasilnya dibutuhkan. Evaluasi malas adalah konsep yang sangat berguna, tetapi memiliki beberapa peringatan, termasuk fakta bahwa mudah untuk melewatkan momen di mana hasilnya akan diperlukan, terutama dalam kasus di mana hasilnya digunakan beberapa kali.Dalam kasus a
foreach
, hasilnya diminta hanya sekali. Dalam kasus afor
sebagaimana diterapkan dalam kode yang ditulis secara tidak benar di atas , hasilnya diminta 94 kali , yaitu 47 × 2:Setiap kali
cities.Count()
disebut (47 kali),Setiap kali
cities.ElementAt(i)
disebut (47 kali).Meminta basis data 94 kali alih-alih yang mengerikan, tetapi bukan hal terburuk yang mungkin terjadi. Bayangkan, misalnya, apa yang akan terjadi jika
select
kueri akan didahului oleh kueri yang juga menyisipkan baris dalam tabel. Benar, kita akan memilikifor
yang akan memanggil database 2.147.483.647 kali, kecuali semoga crash sebelumnya.Tentu saja, kode saya bias. Saya sengaja menggunakan kemalasan
IEnumerable
dan menulisnya dengan cara menelepon berulang kaliListCities()
. Orang dapat mencatat bahwa seorang pemula tidak akan pernah melakukan itu, karena:Tidak
IEnumerable<T>
memiliki propertiCount
, tetapi hanya metodenyaCount()
. Memanggil metode itu menakutkan, dan orang bisa berharap hasilnya tidak akan di-cache, dan tidak cocok difor (; ...; )
blok.Pengindeksan tidak tersedia untuk
IEnumerable<T>
dan tidak jelas untuk menemukanElementAt
metode ekstensi LINQ.Mungkin sebagian besar pemula hanya akan mengubah hasil
ListCities()
menjadi sesuatu yang mereka kenal, seperti aList<T>
.Meski begitu, kode ini sangat berbeda dari
foreach
alternatif. Sekali lagi, ini memberikan hasil yang sama, dan kali iniListCities()
metode ini dipanggil hanya sekali, tetapi menghasilkan 575 item, sementara denganforeach
, itu hanya menghasilkan 47 item.Perbedaannya berasal dari fakta yang
ToList()
menyebabkan semua data dimuat dari basis data. Meskipunforeach
hanya meminta kota sebelum "Boston", yang barufor
meminta semua kota untuk diambil dan disimpan dalam memori. Dengan 575 string pendek, mungkin tidak ada banyak perbedaan, tetapi bagaimana jika kita mengambil hanya beberapa baris dari tabel yang berisi milyaran catatan?Jadi apa
foreach
sebenarnya?foreach
lebih dekat ke loop sementara. Kode yang saya gunakan sebelumnya:dapat dengan mudah diganti dengan:
Keduanya menghasilkan IL yang sama. Keduanya memiliki hasil yang sama. Keduanya memiliki efek samping yang sama. Tentu saja, ini
while
dapat ditulis ulang dalam infinite yang serupafor
, tetapi akan lebih lama dan rawan kesalahan. Anda bebas memilih yang menurut Anda lebih mudah dibaca.Ingin mengujinya sendiri? Berikut kode lengkapnya:
Dan hasilnya:
LINQ vs cara tradisional
Adapun LINQ, Anda mungkin ingin belajar pemrograman fungsional (FP) - bukan hal C # FP, tetapi bahasa FP nyata seperti Haskell. Bahasa fungsional memiliki cara khusus untuk mengekspresikan dan menyajikan kode. Dalam beberapa situasi, ini lebih unggul dari paradigma non-fungsional.
FP diketahui jauh lebih unggul dalam hal memanipulasi daftar ( daftar sebagai istilah umum, yang tidak terkait
List<T>
). Mengingat fakta ini, kemampuan untuk mengekspresikan kode C # dengan cara yang lebih fungsional ketika datang ke daftar agak bagus.Jika Anda tidak yakin, bandingkan dengan keterbacaan kode yang ditulis dengan cara fungsional dan non-fungsional dalam jawaban saya sebelumnya pada subjek.
sumber
foreach
ini lebih efisien daripadafor
, padahal sebenarnya perbedaan tersebut adalah hasil dari kode yang sengaja dipecah. Ketelitian jawaban menebus dirinya sendiri, tetapi mudah untuk melihat bagaimana pengamat kasual mungkin sampai pada kesimpulan yang salah.Meskipun sudah ada beberapa eksposisi besar tentang perbedaan antara for dan foreach. Ada beberapa kesalahan penyajian peran LINQ.
Sintaks LINQ bukan hanya gula sintaksis yang memberikan perkiraan pemrograman fungsional ke C #. LINQ menyediakan konstruksi Fungsional termasuk semua manfaatnya untuk C #. Dikombinasikan dengan mengembalikan IEnumerable dan bukan IList, LINQ menyediakan eksekusi iterasi yang ditangguhkan. Apa yang biasanya dilakukan orang sekarang adalah membuat dan mengembalikan IList dari fungsinya seperti itu
Alih-alih menggunakan sintaks pengembalian hasil untuk membuat enumerasi yang ditangguhkan.
Sekarang pencacahan tidak akan terjadi sampai Anda Daftar atau beralih di atasnya. Dan itu hanya terjadi sesuai kebutuhan (inilah enumerasi Fibbonaci yang tidak memiliki masalah stack overflow)
Melakukan pendahuluan atas fungsi Fibonacci akan mengembalikan urutan 46. Jika Anda ingin ke-30 itu saja yang akan dihitung
Di mana kita dapat bersenang-senang adalah dukungan dalam bahasa untuk ekspresi lambda (dikombinasikan dengan konstruksi IQueryable dan IQueryProvider, ini memungkinkan untuk komposisi fungsional permintaan terhadap berbagai set data, IQueryProvider bertanggung jawab untuk menafsirkan yang disahkan di ekspresi dan membuat dan mengeksekusi kueri menggunakan konstruksi asli sumber). Saya tidak akan masuk ke rincian sepele di sini, tetapi ada serangkaian posting blog yang menunjukkan cara membuat Penyedia Query SQL di sini
Singkatnya, Anda harus memilih mengembalikan IEnumerable daripada IList ketika konsumen fungsi Anda akan melakukan iterasi sederhana. Dan gunakan kapabilitas LINQ untuk menunda eksekusi query kompleks sampai dibutuhkan.
sumber
Keterbacaan ada di mata yang melihatnya. Beberapa orang mungkin berkata
mudah dibaca; yang lain mungkin mengatakan bahwa ini buram, dan lebih suka
membuatnya lebih jelas apa yang sedang dilakukan. Kami tidak dapat memberi tahu Anda apa yang menurut Anda lebih mudah dibaca. Tetapi Anda mungkin dapat mendeteksi beberapa bias saya sendiri dalam contoh yang saya buat di sini ...
sumber
Perbedaan antara LINQ dan
foreach
benar - benar bermuara pada dua gaya pemrograman yang berbeda: imperatif dan deklaratif.Imperatif: dengan gaya ini Anda memberi tahu komputer "lakukan ini ... sekarang lakukan ini ... sekarang lakukan ini sekarang lakukan ini". Anda memberi makan program satu langkah pada satu waktu.
Deklaratif: dalam gaya ini Anda memberi tahu komputer apa yang Anda inginkan hasilnya dan biarkan ia mencari cara untuk sampai ke sana.
Contoh klasik dari kedua gaya ini adalah membandingkan kode assembly (atau C) dengan SQL. Dalam pertemuan Anda memberikan instruksi (secara harfiah) satu per satu. Di SQL Anda menyatakan bagaimana cara menggabungkan data bersama dan hasil apa yang Anda inginkan dari data itu.
Efek samping yang bagus dari pemrograman deklaratif adalah bahwa ia cenderung tingkat yang sedikit lebih tinggi. Ini memungkinkan platform untuk berkembang di bawah Anda tanpa Anda harus mengubah kode. Misalnya:
Apa yang terjadi disini? Apakah Beda menggunakan satu inti? Dua? Lima puluh Kami tidak tahu dan kami tidak peduli. .NET devs dapat menulis ulang kapan saja, selama itu terus melakukan tujuan yang sama kode kita secara ajaib bisa lebih cepat setelah pembaruan kode.
Ini adalah kekuatan pemrograman fungsional. Dan alasan Anda akan menemukan kode itu dalam bahasa seperti Clojure, F # dan C # (ditulis dengan pola pikir pemrograman fungsional) seringkali 3x-10x lebih kecil daripada rekan-rekan yang sangat penting.
Akhirnya saya suka gaya deklaratif karena di C # sebagian besar waktu ini memungkinkan saya untuk menulis kode yang tidak mengubah data. Dalam contoh di atas,
Distinct()
tidak mengubah bilah, ia mengembalikan salinan data baru. Ini berarti bahwa apa pun bilah itu, dan dari mana pun asalnya, tidak tiba-tiba berubah.Jadi seperti yang dikatakan poster lainnya, pelajari pemrograman fungsional. Itu akan mengubah hidup Anda. Dan jika Anda bisa, lakukanlah dalam bahasa pemrograman fungsional yang sebenarnya. Saya lebih suka Clojure, tetapi F # dan Haskell juga pilihan yang sangat baik.
sumber
var foo = bar.Distinct()
pada dasarnya adalahIEnumerator<T>
sampai Anda menelepon.ToList()
atau.ToArray()
. Itu adalah perbedaan penting karena jika Anda tidak menyadarinya, hal itu dapat menyebabkan kesulitan untuk memahami bug.Bisakah pengembang lain dalam tim membaca LINQ?
Jika tidak maka jangan gunakan atau salah satu dari dua hal akan terjadi:
A untuk setiap loop sangat cocok untuk iterasi melalui daftar tetapi jika bukan itu yang harus Anda lakukan maka jangan gunakan satu.
sumber