ถ้า (items! = null) ฟุ่มเฟือยก่อน foreach (รายการ T ในรายการ)?


109

ฉันมักจะเจอรหัสดังต่อไปนี้:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

โดยทั่วไปifเงื่อนไขจะทำให้แน่ใจว่าforeachบล็อกจะดำเนินการต่อเมื่อitemsไม่เป็นโมฆะ ฉันสงสัยว่าifเงื่อนไขนั้นจำเป็นจริงๆหรือforeachจะจัดการกับกรณีนี้หรือitems == nullไม่

ฉันหมายความว่าฉันสามารถเขียน

foreach(T item in items)
{
    //...
}

โดยไม่ต้องกังวลว่าจะitemsเป็นโมฆะหรือไม่? เป็นifฟุ่มเฟือยสภาพ? หรือขึ้นอยู่กับประเภทของitemsหรืออาจจะTด้วย?



1
คำตอบของ @ kjbartel (ที่ " stackoverflow.com/a/32134295/401246 " เป็นทางออกที่ดีที่สุดเพราะไม่: a) เกี่ยวข้องกับการลดประสิทธิภาพของ (แม้ว่าจะไม่null) โดยทั่วไปการวนซ้ำทั้งหมดไปยัง LCD ของEnumerable(ตามที่ใช้??จะ ), b) ต้องการการเพิ่มวิธีการขยายให้กับทุกโครงการหรือ c) ต้องการการหลีกเลี่ยงnull IEnumerables (Pffft! Puh-LEAZE! SMH.) เพื่อเริ่มต้นด้วย (cuz nullหมายถึง N / A ในขณะที่รายการว่างหมายถึงมันเป็นแอปพลิเคชัน แต่คือ ในขณะนี้ก็ว่างเปล่า ! เช่น Empl อาจมีค่าคอมมิชชั่นที่ไม่มีสำหรับการไม่ขายหรือว่างสำหรับการขายเมื่อพวกเขาไม่ได้รับใด ๆ )
ทอม

คำตอบ:


123

คุณยังต้องตรวจสอบว่า (items! = null) ไม่เช่นนั้นคุณจะได้รับ NullReferenceException อย่างไรก็ตามคุณสามารถทำสิ่งนี้ได้:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

แต่คุณอาจตรวจสอบประสิทธิภาพของมัน ดังนั้นฉันยังคงชอบมี if (items! = null) ก่อน

ตามคำแนะนำของ Lippert ของ Eric ฉันเปลี่ยนรหัสเป็น:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}

33
ไอเดียน่ารัก; อาร์เรย์ว่างจะดีกว่าเนื่องจากใช้หน่วยความจำน้อยกว่าและสร้างแรงกดดันของหน่วยความจำน้อยกว่า นับได้ <string> ว่างจะเป็นที่ต้องการมากกว่าเพราะแคชอาร์เรย์ว่างที่สร้างขึ้นและใช้ซ้ำ
Eric Lippert

6
ฉันคาดว่ารหัสที่สองจะช้าลง มันลดลำดับลงIEnumerable<T>ซึ่งจะลดระดับลงเพื่อแจงนับไปยังอินเทอร์เฟซทำให้การวนซ้ำช้าลง การทดสอบของฉันแสดงให้เห็นการย่อยสลายปัจจัย 5 สำหรับการวนซ้ำในอาร์เรย์ int
CodesInChaos

12
@CodeInChaos: โดยทั่วไปคุณพบว่าความเร็วในการระบุลำดับว่างคือปัญหาคอขวดของประสิทธิภาพในโปรแกรมของคุณหรือไม่?
Eric Lippert

16
ไม่เพียงลดความเร็วในการแจงนับของลำดับว่าง แต่ยังรวมถึงลำดับทั้งหมดด้วย และถ้าลำดับยาวพอก็อาจมีความสำคัญ สำหรับรหัสส่วนใหญ่เราควรเลือกรหัสสำนวน แต่การจัดสรรสองรายการที่คุณกล่าวถึงจะเป็นปัญหาด้านประสิทธิภาพในบางกรณีแม้แต่น้อย
CodesInChaos

17
@CodeInChaos: อาฉันเห็นประเด็นของคุณแล้ว เมื่อคอมไพเลอร์สามารถตรวจพบว่า "foreach" กำลังวนซ้ำอยู่บน List <T> หรืออาร์เรย์ก็สามารถเพิ่มประสิทธิภาพ foreach เพื่อใช้ตัวนับชนิดค่าหรือสร้างลูป "for" ได้ เมื่อถูกบังคับให้แจกแจงรายการหรือลำดับว่างก็จะต้องกลับไปที่โค้ดเจน "ตัวส่วนร่วมต่ำสุด" ซึ่งในบางกรณีอาจทำงานช้าลงและทำให้เกิดแรงกดดันในหน่วยความจำมากขึ้น นี่เป็นจุดที่บอบบาง แต่ยอดเยี่ยม แน่นอนว่าคุณธรรมของเรื่องราวคือ - เช่นเคย - หากคุณมีปัญหาเรื่องความสมบูรณ์แบบให้กำหนดรายละเอียดเพื่อค้นหาว่าคอขวดที่แท้จริงคืออะไร
Eric Lippert

75

การใช้ C # 6 คุณสามารถใช้ตัวดำเนินการเงื่อนไขว่างใหม่ร่วมกับList<T>.ForEach(Action<T>)(หรือIEnumerable<T>.ForEachวิธีการขยายของคุณเอง)

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});

คำตอบที่หรูหรา ขอบคุณ!
Steve

2
นี่เป็นทางออกที่ดีที่สุดเพราะไม่ได้: a) เกี่ยวข้องกับการลดประสิทธิภาพของ (แม้ว่าจะไม่null) การรวมลูปทั้งหมดไปยัง LCD ของEnumerable(ตามที่ใช้??) b) จำเป็นต้องเพิ่มวิธีการขยายให้กับทุกโครงการหรือ c ) ต้องการการหลีกเลี่ยงnull IEnumerables (Pffft! Puh-LEAZE! SMH.) เพื่อเริ่มต้นด้วย (cuz nullหมายถึง N / A ในขณะที่รายการว่างหมายถึงมันเป็นแอปพลิเคชัน แต่ตอนนี้ว่างเปล่า ! กล่าวคือ Empl อาจมีค่าคอมมิชชั่นที่ N / A สำหรับการไม่ขายหรือว่างสำหรับการขายเมื่อพวกเขาไม่ได้รับใด ๆ )
ทอม

7
@ Tom: ก็ถือว่าitemsเป็นแต่แทนที่จะเป็นเพียงใดList<T> IEnumerable<T>(หรือมีวิธีการขยายที่กำหนดเองซึ่งคุณบอกว่าคุณไม่ต้องการให้มี ... ) นอกจากนี้ฉันขอบอกว่ามันไม่คุ้มที่จะเพิ่มความคิดเห็น 11รายการโดยทั่วไปแล้วบอกว่าคุณชอบคำตอบเฉพาะ
Jon Skeet

3
@ ทอม: ฉันขอไม่แนะนำให้คุณทำเช่นนั้นในอนาคต ลองนึกภาพว่าทุกคนที่ไม่เห็นด้วยกับความคิดเห็นของคุณแล้วเพิ่มความคิดเห็นของพวกเขาในความคิดเห็นของคุณทั้งหมด (ลองนึกภาพว่าฉันเขียนตอบกลับที่นี่ แต่ 11 ครั้ง) นี่ไม่ใช่การใช้ Stack Overflow อย่างมีประสิทธิผล
Jon Skeet

2
foreachฉันยังถือว่ามีประสิทธิภาพการทำงานที่ต้องการจะตีเรียกผู้รับมอบสิทธิ์เมื่อเทียบกับมาตรฐาน โดยเฉพาะอย่างยิ่งสำหรับรายการที่ฉันคิดว่าได้รับการแปลงเป็นforลูป
kjbartel

38

Takeaway ที่จริงที่นี่ควรจะเป็นลำดับที่เกือบจะไม่ควรจะเป็นโมฆะในสถานที่แรก เพียงทำให้มันไม่แปรผันในทุกโปรแกรมของคุณถ้าคุณมีลำดับมันจะไม่เป็นโมฆะ มันจะเริ่มต้นเสมอเพื่อเป็นลำดับว่างหรือลำดับของแท้อื่น ๆ

หากลำดับไม่เป็นโมฆะแน่นอนคุณไม่จำเป็นต้องตรวจสอบ


2
แล้วถ้าคุณได้ลำดับจากบริการ WCF ล่ะ? มันอาจจะเป็นโมฆะใช่ไหม?
Nawaz

4
@Nawaz: ถ้าฉันมีบริการ WCF ที่ส่งคืนลำดับว่างของฉันโดยตั้งใจให้เป็นลำดับว่างฉันจะรายงานให้พวกเขาทราบว่าเป็นข้อผิดพลาด ที่กล่าวว่า: หากคุณต้องจัดการกับผลลัพธ์ที่ไม่ดีของบริการที่มีเนื้อหาผิดพลาดใช่คุณต้องจัดการกับมันโดยการตรวจสอบค่าว่าง
Eric Lippert

7
เว้นแต่แน่นอนว่าโมฆะและว่างเปล่าหมายถึงสิ่งที่แตกต่างกันอย่างสิ้นเชิง บางครั้งก็ใช้ได้สำหรับลำดับ
Configurator

@Nawaz วิธีการเกี่ยวกับ DataTable.Rows ที่คืนค่า null แทนที่จะเป็นคอลเลกชันว่างเปล่า อาจจะเป็นบั๊ก?
Neil B

คำตอบของ @ kjbartel (ที่ " stackoverflow.com/a/32134295/401246 " เป็นทางออกที่ดีที่สุดเพราะไม่: a) เกี่ยวข้องกับการลดประสิทธิภาพของ (แม้ว่าจะไม่null) โดยทั่วไปการวนซ้ำทั้งหมดไปยัง LCD ของEnumerable(ตามที่ใช้??จะ ), b) ต้องการการเพิ่มวิธีการขยายให้กับทุกโครงการหรือ c) ต้องการการหลีกเลี่ยงnull IEnumerables (Pffft! Puh-LEAZE! SMH.) เพื่อเริ่มต้นด้วย (cuz nullหมายถึง N / A ในขณะที่รายการว่างหมายถึงมันเป็นแอปพลิเคชัน แต่คือ ในขณะนี้ก็ว่างเปล่า ! เช่น Empl อาจมีค่าคอมมิชชั่นที่ไม่มีสำหรับการไม่ขายหรือว่างสำหรับการขายเมื่อพวกเขาไม่ได้รับใด ๆ )
ทอม

10

จริงๆแล้วมีการร้องขอคุณสมบัติใน @Connect นั้น: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

และการตอบสนองค่อนข้างสมเหตุสมผล:

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


ฉันเดาว่ามีข้อดีและข้อเสียสำหรับสิ่งนี้ดังนั้นพวกเขาจึงตัดสินใจที่จะเก็บมันไว้ตามที่ออกแบบไว้ตั้งแต่แรก ท้ายที่สุด foreach เป็นเพียงน้ำตาลที่เป็นประโยค หากคุณได้เรียกใช้ items.GetEnumerator () ซึ่งอาจเกิดข้อผิดพลาดหากรายการเป็นโมฆะดังนั้นคุณต้องทดสอบก่อน
Marius Bancila

6

คุณสามารถทดสอบด้วยรายการว่างได้ตลอดเวลา ... แต่นี่คือสิ่งที่ฉันพบในเว็บไซต์ msdn

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

ถ้านิพจน์มีค่า null System.NullReferenceException จะถูกโยนทิ้ง


2

มันไม่ฟุ่มเฟือย ในรายการรันไทม์จะถูกแคสต์ไปยัง IEnumerable และเมธอด GetEnumerator จะถูกเรียกใช้ ซึ่งจะทำให้การอ้างอิงรายการที่จะล้มเหลว


1
1) ลำดับจะไม่จำเป็นต้องถูกส่งไปยังIEnumerableและ 2) เป็นการตัดสินใจออกแบบที่จะโยนทิ้ง C # สามารถแทรกได้อย่างง่ายดายnullตรวจสอบว่านักพัฒนาคิดว่าเป็นความคิดที่ดี
CodesInChaos

2

คุณสามารถห่อหุ้มการตรวจสอบค่าว่างในวิธีการขยายและใช้แลมด้า:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

รหัสจะกลายเป็น:

items.ForEach(item => { 
  ...
});

หากสามารถกระชับได้มากขึ้นหากคุณต้องการเรียกใช้เมธอดที่รับไอเท็มและส่งคืนvoid:

items.ForEach(MethodThatTakesAnItem);

1

คุณต้องการสิ่งนี้ คุณจะได้รับข้อยกเว้นเมื่อforeachเข้าถึงคอนเทนเนอร์เพื่อตั้งค่าการทำซ้ำเป็นอย่างอื่น

ภายใต้ฝาครอบforeachใช้อินเทอร์เฟซที่ติดตั้งบนคลาสคอลเลกชันเพื่อดำเนินการวนซ้ำ อินเตอร์เฟซที่เทียบเท่าทั่วไปคือที่นี่

คำสั่ง foreach ของภาษา C # (สำหรับแต่ละรายการใน Visual Basic) ซ่อนความซับซ้อนของตัวนับ ดังนั้นจึงแนะนำให้ใช้ foreach แทนการจัดการกับตัวแจงนับโดยตรง


1
เช่นเดียวกับที่ทราบว่าในทางเทคนิคไม่ได้ใช้อินเทอร์เฟซจะใช้การพิมพ์แบบเป็ด: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspxอินเทอร์เฟซช่วยให้มั่นใจได้ว่ามีวิธีการและคุณสมบัติที่ถูกต้อง แม้ว่าและช่วยให้เข้าใจเจตนา เช่นเดียวกับการใช้ foreach ภายนอก ...
ShuggyCoUk

0

การทดสอบเป็นสิ่งที่จำเป็นเพราะถ้าคอลเล็กชันเป็นโมฆะ foreach จะส่ง NullReferenceException มันค่อนข้างง่ายที่จะทดลองใช้

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}



0

ใน C # 6 คุณสามารถเขียน sth ดังนี้:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

โดยพื้นฐานแล้วเป็นวิธีแก้ปัญหาของ Vlad Bezden แต่ใช้ ?? นิพจน์เพื่อสร้างอาร์เรย์ที่ไม่เป็นโมฆะเสมอดังนั้นจึงอยู่รอดของ foreach แทนที่จะมีการตรวจสอบนี้ภายในวงเล็บ foreach

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