Jika saya menulis sesuatu seperti ini:
var things = mythings
.Where(x => x.IsSomeValue)
.Where(y => y.IsSomeOtherValue)
Apakah ini sama dengan:
var results1 = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue)
results1.Add(t);
var results2 = new List<Thing>();
foreach(var t in results1)
if(t.IsSomeOtherValue)
results2.Add(t);
Atau ada sihir di balik selimut yang berfungsi lebih seperti ini:
var results = new List<Thing>();
foreach(var t in mythings)
if(t.IsSomeValue && t.IsSomeOtherValue)
results.Add(t);
Atau apakah itu sesuatu yang sama sekali berbeda?
Jawaban:
Pertanyaan LINQ malas . Itu berarti kode:
sangat sedikit. Enumerable asli (
mythings
) hanya disebutkan ketika enumerable yang dihasilkan (things
) dikonsumsi, misalnya denganforeach
loop.ToList()
,, atau.ToArray()
.Jika Anda menelepon
things.ToList()
, ini kira-kira setara dengan kode terakhir Anda, dengan mungkin beberapa (biasanya tidak signifikan) overhead dari enumerator.Demikian juga, jika Anda menggunakan loop foreach:
Ini serupa dalam kinerja dengan:
Beberapa keuntungan kinerja dari pendekatan kemalasan untuk enumerables (sebagai lawan menghitung semua hasil dan menyimpannya dalam daftar) adalah bahwa ia menggunakan memori yang sangat sedikit (karena hanya satu hasil disimpan pada satu waktu) dan bahwa tidak ada peningkatan signifikan - biaya awal.
Jika enumerable hanya disebutkan sebagian, ini sangat penting. Pertimbangkan kode ini:
Cara LINQ diimplementasikan,
mythings
hanya akan disebutkan hingga elemen pertama yang cocok dengan kondisi tempat Anda. Jika elemen tersebut berada di awal daftar, ini bisa menjadi peningkatan kinerja yang sangat besar (misalnya O (1), bukan O (n)).sumber
foreach
adalah bahwa LINQ menggunakan permintaan delegasi, yang memiliki beberapa overhead. Ini bisa menjadi signifikan ketika kondisi mengeksekusi sangat cepat (yang sering mereka lakukan).ToList
atauToArray
. Jika hal seperti itu telah dibangun dengan benarIEnumerable
, akan mungkin untuk meminta daftar untuk "memotret" segala aspek yang mungkin berubah di masa depan tanpa harus menghasilkan semuanya.Kode berikut:
Setara dengan tidak ada, karena evaluasi malas, tidak ada yang akan terjadi.
Berbeda, karena evaluasi akan diluncurkan.
Setiap item
mythings
akan diberikan kepada yang pertamaWhere
. Jika lewat, itu akan diberikan kepada yang keduaWhere
. Jika lewat, itu akan menjadi bagian dari output.Jadi ini terlihat seperti ini:
sumber
Di samping eksekusi yang ditangguhkan (yang sudah dijelaskan oleh jawaban lain, saya hanya akan menunjukkan detail lain), lebih seperti pada contoh kedua Anda.
Mari kita bayangkan Anda menelepon
ToList
dithings
.Implementasi
Enumerable.Where
pengembalian aEnumerable.WhereListIterator
. Ketika Anda memanggilWhere
ituWhereListIterator
(alias chainingWhere
-calls), Anda tidak lagi meneleponEnumerable.Where
, tetapiEnumerable.WhereListIterator.Where
, yang sebenarnya menggabungkan predikat (menggunakanEnumerable.CombinePredicates
).Jadi lebih seperti
if(t.IsSomeValue && t.IsSomeOtherValue)
.sumber
Tidak itu tidak sama. Dalam contoh Anda
things
adalahIEnumerable
, yang pada saat ini masih hanya iterator, bukan array atau daftar aktual. Apalagi karenathings
tidak digunakan, loop bahkan tidak pernah dievaluasi. Jenis iniIEnumerable
memungkinkan untuk beralih melalui elemen-elemenyield
oleh instruksi Linq dan memprosesnya lebih lanjut dengan lebih banyak instruksi, yang berarti pada akhirnya Anda hanya memiliki satu loop.Tetapi segera setelah Anda menambahkan instruksi seperti
.ToArray()
atau.ToList()
, Anda memesan pembuatan struktur data aktual, sehingga menempatkan batasan pada rantai Anda.Lihat pertanyaan SO terkait ini: /programming/2789389/how-do-i-implement-ienumerable
sumber