Foreach döngülerinin filtrelenmesi, şartların devam etmesine karşın, koruma cümleciklerine devam edilmesi.


24

Bazı programcıların bunu kullandığını gördüm:

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

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

    // code
}

normalde kullanacağım yer yerine:

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

İkisinin bir kombinasyonunu bile gördüm. 'Devam' ile okunabilirliği gerçekten çok seviyorum, özellikle daha karmaşık koşullarda. Performansta bile bir fark var mı? Bir veritabanı sorgusu ile olacağını tahmin ediyorum. Normal listelerden ne haber?


3
Düzenli listeler için mikro optimizasyona benziyor.
kıyamet

2
@zgnilec: ... ama aslında iki değişkenden hangisi optimize edilmiş versiyondur? Elbette bu konuda bir fikrim var, ama sadece koda bakmaktan dolayı bu herkes için kesin olarak net değil.
Doktor Brown,

2
Tabii devam etmek daha hızlı olacak. Linq kullanma. Ek yineleyici yarattığınız yer.
kıyamet

1
@ Zgnilec - İyi teori. Neden böyle düşündüğünü açıklayan bir cevap göndermek ister misin? Şu anda varolan her iki cevap da tam tersini söylüyor.
Bobson,

2
... sonuç şu ki: iki yapı arasındaki performans farklılıkları ihmal edilebilir ve her ikisi için de okunabilirliğin yanı sıra okunabilirlik de sağlanabilir. Bu sadece hangisini tercih edeceğiniz bir zevk meselesidir.
Doktor Brown,

Yanıtlar:


63

Bunu komut / sorgu ayırma kullanmak için uygun bir yer olarak kabul ediyorum . Örneğin:

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

Bu ayrıca, sorgu sonucuna iyi bir kendi kendine belgeleme adı vermenize olanak sağlar. Ayrıca, yeniden yapılanma fırsatlarını görmenize yardımcı olur, çünkü yalnızca verileri sorgulayan veya yalnızca mutasyona uğramış verileri kodlamayı yeniden düzenlemek, her ikisini de yapmaya çalışan karma koddan daha kolaydır.

Hata ayıklama yaparken foreach, içeriğinin validItemsbeklediğiniz çözüme göre çözülüp çözülmediğini hızlı bir şekilde kontrol etmek için daha önce kırabilirsiniz . Gerekmedikçe lambdaya girmek zorunda değilsin. Eğer lambdaya girmeniz gerekiyorsa, onu ayrı bir işleve ayırmanızı öneririm, o zaman bunun yerine ilerleyin.

Performansta bir fark var mı? Sorgu bir veritabanı tarafından destekleniyorsa, LINQ sürümünün daha hızlı çalıştırma potansiyeli vardır , çünkü SQL sorgusu daha verimli olabilir . Nesnelere LINQ ise, o zaman herhangi bir gerçek performans farkı görmezsiniz. Her zaman olduğu gibi, optimizasyonları önceden tahmin etmeyi denemek yerine kodunuzu belirleyin ve gerçekte bildirilen darboğazları düzeltin.


1
Çok büyük bir veri kümesi neden fark yaratır? Sırf lambdaların asgari maliyeti, nihayetinde toplanacağı için mi?
BlueRaja - Danny Pflughoeft

1
@ BlueRaja-DannyPflughoeft: Evet haklısınız, bu örnek orijinal kodun ötesinde ek bir algoritmik karmaşıklık içermiyor. İfadeyi kaldırdım.
Christian Hayter,

Bu koleksiyon üzerinde iki yineleme ile sonuçlanmıyor mu? Doğal olarak, ikincisi daha kısadır, içinde yalnızca geçerli öğeler olduğu düşünülür, ancak öğeleri filtrelemek için ikinci kez geçerli öğelerle çalışmak için ikinci kez yine de iki kez yapmanız gerekir.
Andy,

1
@DavidPacker Hayır . Yalnızca döngü IEnumerabletarafından sürülüyor foreach.
Benjamin Hodgson

2
@DavidPacker: Aynen öyle; LINQ to Objects yöntemlerinin çoğu yineleyici blokları kullanılarak uygulanır. Yukarıdaki örnek kod, Whereelement başına bir kere lambda ve ilmek gövdesini (eğer lambda doğru dönerse) uygulayarak, koleksiyon boyunca tam olarak bir kez yinelenir.
Christian Hayter

7

Tabii ki performansta bir fark var, .Where()her bir öğe için bir temsilci çağrısı yapılmasıyla sonuçlanıyor. Ancak, performans hakkında hiç endişelenmiyorum:

  • Bir temsilcinin çağrılmasında kullanılan saat döngüleri, koleksiyon üzerinde yinelenen ve koşulları denetleyen kodun geri kalanının kullandığı saat döngüleriyle karşılaştırıldığında önemsizdir.

  • Bir temsilciyi çağırma performansının cezası, birkaç saat döngüsünün sırasına bağlıdır ve neyse ki, bireysel saat döngüleri için endişelenmemiz gereken günleri çoktan geride bıraktık.

Nedense performansı ise gerçekten o zaman kullanmak, saat döngüsü düzeyinde sizin için önemli List<Item>yerine IList<Item>derleyici yerine sanal aramaların çağırır ve yineleyici böylece doğrudan (ve inlinable) yararlanabilirler böylece, List<T>aslında hangi a struct, kutulu olmak zorunda değildir. Ama bu gerçekten önemsiz şeyler.

Bir veritabanı sorgusu farklı bir durumdur, çünkü (en azından teoride) filtrenin RDBMS'ye gönderilmesi olasılığı vardır, bu nedenle performansı büyük ölçüde iyileştirir: sadece eşleşen satırlar RDBMS'den programınıza geçişi sağlar. Ancak bunun için linq kullanmanız gerektiğini düşünüyorum, bu ifadenin olduğu gibi RDBMS'ye gönderilebileceğini sanmıyorum.

if(x) continue;Bu kodda hata ayıklamak zorunda olduğunuz anın faydalarını gerçekten göreceksiniz : Tek adım atma if()s ve continues iyi çalışıyor; filtreleme temsilcisine tek adım atmak bir acıdır.


Bu bir şeylerin yanlış olduğu ve tüm öğelere bakmak ve hangilerinin Field! = Null ve hangilerinin State! = Null olduğu hata ayıklayıcısını kontrol etmek istediğinizde gerçekleşir. bunun Foreach ile imkansız olması zor olabilir ... nerede.
gnasher729

Hata ayıklama ile iyi bir nokta. Bir yere adım atmak Visual Studio'da o kadar da kötü değil, ancak yeniden derlemeden hata ayıklama yaparken lambda ifadelerini yeniden yazamazsınız ve bunu kullanırken kaçınılmazsınız if(x) continue;.
Paprik

Kesin konuşursak, .Whereyalnızca bir kez çağrılır. Her yinelemede çağrılan şey filtre MoveNextCurrent
devridir

@CodesInChaos, neden bahsettiğinizi anlamayı biraz düşünmemi sağladı, ama elbette, wh00ps, haklısınız, kesinlikle konuşuyorsunuz, .Wheresadece bir kez çağrılmaya başladınız . Onu düzeltti.
Mike Nakis,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.