Memfilter loop foreach dengan kondisi di mana vs melanjutkan klausa penjaga

24

Saya telah melihat beberapa programmer menggunakan ini:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

alih-alih di tempat yang biasanya saya gunakan:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

Saya bahkan telah melihat kombinasi keduanya. Saya sangat suka keterbacaan dengan 'terus', terutama dengan kondisi yang lebih kompleks. Apakah ada perbedaan kinerja? Dengan permintaan basis data, saya berasumsi akan ada. Bagaimana dengan daftar reguler?

Paprik
sumber
3
Untuk daftar reguler kedengarannya seperti optimasi mikro.
kiamat
2
@ zgnilec: ... tetapi sebenarnya yang mana dari dua varian yang merupakan versi yang dioptimalkan? Saya punya pendapat tentang itu, tentu saja, tetapi dari hanya melihat kode ini tidak jelas secara inheren untuk semua orang.
Doc Brown
2
Tentu saja melanjutkan akan lebih cepat. Menggunakan linq. Di mana Anda membuat iterator tambahan.
kiamat
1
@ zgnilec - Teori yang bagus. Ingin memposting jawaban yang menjelaskan mengapa Anda berpikir begitu? Kedua jawaban yang ada saat ini mengatakan sebaliknya.
Bobson
2
... jadi intinya adalah: perbedaan kinerja antara kedua konstruksi diabaikan, dan keterbacaan serta debuggabilitas dapat dicapai untuk keduanya. Ini hanyalah masalah selera yang mana yang Anda sukai.
Doc Brown

Jawaban:

64

Saya akan menganggap ini sebagai tempat yang tepat untuk menggunakan pemisahan perintah / permintaan . Sebagai contoh:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

Ini juga memungkinkan Anda untuk memberikan nama yang mendokumentasikan diri sendiri dengan baik ke hasil kueri. Ini juga membantu Anda melihat peluang untuk refactoring, karena jauh lebih mudah untuk melakukan refactor kode yang hanya menanyakan data atau hanya mengubah data daripada kode campuran yang mencoba melakukan keduanya.

Saat men-debug, Anda dapat memutuskan sebelum foreachdengan cepat memeriksa apakah isi dari validItemspenyelesaian sesuai dengan yang Anda harapkan. Anda tidak harus masuk ke lambda kecuali Anda perlu. Jika Anda perlu masuk ke dalam lambda, maka saya sarankan untuk memfaktorkannya ke dalam fungsi yang terpisah, kemudian melangkah melalui itu.

Apakah ada perbedaan kinerja? Jika kueri didukung oleh database, maka versi LINQ memiliki potensi untuk berjalan lebih cepat, karena kueri SQL mungkin lebih efisien. Jika itu LINQ untuk Objek, maka Anda tidak akan melihat perbedaan kinerja nyata. Seperti biasa, buat profil kode Anda dan perbaiki kemacetan yang sebenarnya dilaporkan, alih-alih mencoba memprediksi optimisasi sebelumnya.

Christian Hayter
sumber
1
Mengapa kumpulan data yang sangat besar akan membuat perbedaan? Hanya karena biaya yang sangat kecil dari lambda akhirnya akan bertambah?
BlueRaja - Danny Pflughoeft
1
@ BlueRaja-DannyPflughoeft: Ya Anda benar, contoh ini tidak melibatkan kompleksitas algoritme tambahan di luar kode asli. Saya telah menghapus frasa.
Christian Hayter
Bukankah ini menghasilkan dua iterasi koleksi? Tentu saja, yang kedua lebih pendek, mengingat hanya item yang valid yang ada di dalamnya, tetapi Anda masih perlu melakukannya dua kali, sekali untuk menyaring elemen, kedua kalinya untuk bekerja dengan item yang valid.
Andy
1
@ DavidPacker No. IEnumerablesedang didorong oleh foreachloop saja.
Benjamin Hodgson
2
@ Davidvider: Itulah yang dilakukannya; sebagian besar metode LINQ to Objects diimplementasikan menggunakan blok iterator. Contoh kode di atas akan mengulangi koleksi tepat sekali, mengeksekusi Wherelambda dan loop body (jika lambda mengembalikan true) sekali per elemen.
Christian Hayter
7

Tentu saja ada perbedaan dalam kinerja, .Where()menghasilkan panggilan delegasi yang dibuat untuk setiap item. Namun, saya tidak akan khawatir sama sekali tentang kinerja:

  • Siklus jam yang digunakan dalam memanggil delegasi dapat diabaikan dibandingkan dengan siklus jam yang digunakan oleh sisa kode yang diputar selama pengumpulan dan memeriksa kondisi.

  • Hukuman kinerja untuk memanggil seorang delegasi adalah urutan beberapa siklus clock, dan untungnya, kita sudah melewati hari-hari ketika kita harus khawatir tentang siklus clock individual.

Jika karena alasan tertentu kinerja benar - benar penting bagi Anda pada tingkat siklus clock, maka gunakan List<Item>alih-alih IList<Item>, sehingga kompiler dapat menggunakan panggilan langsung (dan tidak dapat ditelepon) alih-alih panggilan virtual, dan agar iterator List<T>, yang sebenarnya a struct, tidak harus kotak. Tapi itu hal-hal sepele.

Permintaan basis data adalah situasi yang berbeda, karena ada (setidaknya secara teori) kemungkinan mengirim filter ke RDBMS, sehingga sangat meningkatkan kinerja: hanya baris yang cocok yang akan melakukan perjalanan dari RDBMS ke program Anda. Tetapi untuk itu saya pikir Anda harus menggunakan LINQ, saya tidak berpikir ungkapan ini bisa dikirim ke RDBMS seperti apa adanya.

Anda akan benar-benar melihat manfaat dari if(x) continue;saat Anda harus men-debug kode ini: Single-stepping over if()and continues berfungsi dengan baik; satu langkah ke dalam delegasi penyaringan adalah rasa sakit.

Mike Nakis
sumber
Saat itulah ada sesuatu yang salah dan Anda ingin melihat semua item dan memeriksa debugger mana yang memiliki Field! = Null, dan mana yang memiliki Status! = Null; ini mungkin sulit untuk mustahil dengan foreach ... di mana.
gnasher729
Poin bagus dengan debugging. Melangkah ke tempat yang tidak terlalu buruk di Visual Studio, tetapi Anda tidak bisa menulis ulang ekspresi lambda saat debugging tanpa kompilasi ulang dan ini Anda hindari saat menggunakan if(x) continue;.
Paprik
Sebenarnya, .Wherehanya dipanggil sekali. Apa yang dipanggil pada setiap iterasi adalah delegasi filter ( MoveNextdan Currentpada enumerator, ketika mereka tidak dioptimalkan)
CodesInChaos
@CodesInChaos butuh sedikit pemikiran untuk memahami apa yang Anda bicarakan, tapi tentu saja, wh00ps, Anda benar, sebenarnya, .Wherehanya dipanggil sekali. Memperbaikinya.
Mike Nakis