การกรองลูป foreach ด้วยเงื่อนไขที่ vs clause ต่อไป


24

ฉันเห็นโปรแกรมเมอร์บางคนใช้สิ่งนี้:

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

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

    // code
}

แทนที่จะใช้ที่ฉันปกติ:

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

ฉันเคยเห็นการรวมกันของทั้งสอง ฉันชอบการอ่านด้วยคำว่า 'ดำเนินการต่อ' โดยเฉพาะอย่างยิ่งกับเงื่อนไขที่ซับซ้อน มีความแตกต่างในประสิทธิภาพหรือไม่ ด้วยแบบสอบถามฐานข้อมูลฉันสมมติว่าจะมี สิ่งที่เกี่ยวกับรายการปกติ?


3
สำหรับรายการปกติดูเหมือนว่าการเพิ่มประสิทธิภาพขนาดเล็ก
Apocalypse

2
@zgnilec: ... แต่จริงๆแล้วหนึ่งในสองรุ่นนี้เป็นรุ่นที่ได้รับการปรับปรุงหรือไม่ แน่นอนว่าฉันมีความเห็นเกี่ยวกับเรื่องนี้ แต่จากการดูรหัสนี้ไม่ชัดเจนสำหรับทุกคน
Doc Brown

2
แน่นอนว่าจะดำเนินต่อไปเร็วขึ้น ใช้ linq. ในกรณีที่คุณสร้างตัววนซ้ำเพิ่มเติม
Apocalypse

1
@zgnilec - ทฤษฎีที่ดี สนใจโพสต์คำตอบอธิบายว่าทำไมคุณถึงคิดอย่างนั้น? คำตอบทั้งสองซึ่งปัจจุบันมีอยู่พูดตรงข้าม
Bobson

2
... ดังนั้นสิ่งที่สำคัญที่สุดคือ: ความแตกต่างของประสิทธิภาพระหว่างสิ่งก่อสร้างทั้งสองนั้นถูกละเลยและความสามารถในการอ่านได้ มันเป็นเพียงเรื่องของรสนิยมที่คุณต้องการ
Doc Brown

คำตอบ:


63

ฉันจะเชื่อว่านี่เป็นสถานที่ที่เหมาะสมที่จะใช้คำสั่งแยกแบบสอบถาม / ตัวอย่างเช่น:

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

นอกจากนี้ยังช่วยให้คุณสามารถตั้งชื่อเอกสารด้วยตนเองที่ดีให้กับผลลัพธ์การสืบค้น นอกจากนี้ยังช่วยให้คุณมองเห็นโอกาสในการปรับโครงสร้างอีกครั้งเนื่องจากรหัส refactor นั้นง่ายกว่าที่จะสืบค้นข้อมูลหรือแปลงข้อมูลเพียงรหัสผสมที่พยายามทำทั้งสองอย่าง

เมื่อทำการดีบั๊กคุณสามารถหยุดก่อนที่foreachจะตรวจสอบได้อย่างรวดเร็วว่าเนื้อหาของการvalidItemsแก้ไขสิ่งที่คุณคาดหวัง คุณไม่จำเป็นต้องก้าวเข้าไปในแลมบ์ดาเว้นแต่ว่าคุณต้องการ หากคุณจำเป็นต้องก้าวเข้าไปในแลมบ์ดาฉันขอแนะนำให้แยกออกเป็นฟังก์ชั่นแยกต่างหากจากนั้นค่อยก้าวผ่านสิ่งนั้นแทน

มีความแตกต่างในประสิทธิภาพหรือไม่ หากแบบสอบถามได้รับการสนับสนุนโดยฐานข้อมูลดังนั้นรุ่น LINQ มีโอกาสที่จะทำงานได้เร็วขึ้นเนื่องจากแบบสอบถาม SQL อาจมีประสิทธิภาพมากกว่า หากเป็น LINQ ไปยัง Objects คุณจะไม่เห็นความแตกต่างด้านประสิทธิภาพที่แท้จริง เช่นเคยโปรไฟล์รหัสของคุณและแก้ไขปัญหาคอขวดที่มีการรายงานจริงแทนที่จะพยายามทำนายการเพิ่มประสิทธิภาพล่วงหน้า


1
ทำไมชุดข้อมูลที่มีขนาดใหญ่มากจึงสร้างความแตกต่าง เพียงเพราะค่าใช้จ่ายเล็กน้อยของลูกแกะในที่สุดก็จะเพิ่มขึ้น?
BlueRaja - Danny Pflughoeft

1
@ BlueRaja-DannyPflughoeft: ใช่แล้วตัวอย่างนี้ไม่เกี่ยวข้องกับความซับซ้อนของอัลกอริทึมเพิ่มเติมนอกเหนือจากรหัสต้นฉบับ ฉันลบวลีนี้แล้ว
Christian Hayter

สิ่งนี้ไม่ส่งผลให้เกิดการวนซ้ำสองครั้งในคอลเล็กชันใช่หรือไม่ โดยธรรมชาติแล้วรายการที่สองจะสั้นกว่าโดยพิจารณาจากรายการที่ถูกต้องเท่านั้น แต่คุณยังต้องทำสองครั้งเพื่อกรององค์ประกอบครั้งที่สองที่จะทำงานกับรายการที่ถูกต้อง
Andy

1
@DavidPacker ไม่ The IEnumerableกำลังถูกขับเคลื่อนโดยการforeachวนซ้ำเท่านั้น
Benjamin Hodgson

2
@DavidPacker: นั่นคือสิ่งที่มันทำ วิธี LINQ to Objects ส่วนใหญ่จะใช้งานโดยใช้ตัววนซ้ำ โค้ดตัวอย่างด้านบนจะวนซ้ำในคอลเล็กชันหนึ่งครั้งโดยเรียกใช้Whereแลมบ์ดาและลำตัววนรอบ (หากแลมบ์ดาคืนค่าจริง) หนึ่งครั้งต่อองค์ประกอบ
Christian Hayter

7

แน่นอนว่าประสิทธิภาพการทำงานแตกต่างกัน.Where()ส่งผลให้มีการโทรแบบมอบหมายสำหรับทุกรายการ อย่างไรก็ตามฉันไม่ต้องกังวลเลยเกี่ยวกับประสิทธิภาพ:

  • รอบสัญญาณนาฬิกาที่ใช้ในการเรียกผู้แทนมีความสำคัญน้อยเมื่อเทียบกับรอบสัญญาณนาฬิกาที่ใช้โดยรหัสที่เหลือที่วนซ้ำในการรวบรวมและตรวจสอบเงื่อนไข

  • บทลงโทษที่แสดงถึงการเรียกผู้ร่วมประชุมนั้นเป็นไปตามลำดับของรอบสัญญาณนาฬิกาสองสามรอบและโชคดีที่เราต้องผ่านพ้นวันเวลาที่เราต้องกังวลเกี่ยวกับรอบนาฬิกาแต่ละรอบ

หากมีเหตุผลบางอย่างที่ประสิทธิภาพการทำงานจริงๆสิ่งสำคัญสำหรับคุณในระดับวงจรนาฬิกาแล้วใช้List<Item>แทนIList<Item>เพื่อให้คอมไพเลอร์สามารถใช้โดยตรง (และ inlinable) เรียกแทนการโทรเสมือนจริงและเพื่อให้ iterator ของList<T>ที่เป็นจริงstructไม่ได้จะต้องมีการบรรจุกล่อง แต่นั่นเป็นเรื่องขี้ประติ๋วจริงๆ

แบบสอบถามฐานข้อมูลเป็นสถานการณ์ที่แตกต่างกันเนื่องจากมีความเป็นไปได้ในการส่งตัวกรองไปยัง RDBMS (อย่างน้อยในทางทฤษฎี) ดังนั้นจึงปรับปรุงประสิทธิภาพอย่างมาก: การจับคู่แถวเท่านั้นจะทำให้การเดินทางจาก RDBMS ไปยังโปรแกรมของคุณ แต่สำหรับที่ฉันคิดว่าคุณจะต้องใช้ linq ฉันไม่คิดว่าการแสดงออกนี้สามารถส่งไปยัง RDBMS ตามที่เป็นอยู่

คุณจะเห็นประโยชน์ของif(x) continue;ช่วงเวลาที่คุณต้องแก้ไขข้อบกพร่องของรหัสนี้: การทำงานแบบif()s และcontinues อย่างเดียว การก้าวเข้าสู่ผู้แทนจากการกรองเป็นความเจ็บปวด


นั่นคือเมื่อมีบางสิ่งผิดปกติและคุณต้องการดูรายการทั้งหมดและตรวจสอบในโปรแกรมดีบั๊กที่มีฟิลด์! = null และรายการใดมีสถานะ! = null; นี่อาจเป็นเรื่องยากที่จะเป็นไปไม่ได้เมื่ออยู่กับ ...
gnasher729

จุดที่ดีกับการแก้จุดบกพร่อง การก้าวไปสู่จุดที่ไม่เลวใน Visual Studio แต่คุณไม่สามารถเขียนแลมบ์ดานิพจน์ใหม่ได้ในขณะที่ทำการดีบั๊กโดยไม่ต้องทำการคอมไพล์if(x) continue;ซ้ำและสิ่งนี้คุณหลีกเลี่ยงเมื่อใช้
Paprik

พูดอย่างเคร่งครัด.Whereจะถูกเรียกเพียงครั้งเดียว สิ่งที่ได้รับในแต่ละเรียกซ้ำเป็นผู้รับมอบสิทธิ์กรอง (และMoveNextและCurrentในการแจงนับเมื่อพวกเขาไม่ได้รับการปรับให้เหมาะสมออก)
CodesInChaos

@CodesInChaos ฉันคิดว่าจะเข้าใจสิ่งที่คุณพูดถึง แต่แน่นอนว่า wh00ps คุณพูดถูกต้องพูดอย่างเคร่งครัด.Whereจะถูกเรียกเพียงครั้งเดียว ซ่อมมัน.
Mike Nakis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.