ตรวจหาค่าว่างใน foreach loop


97

มีวิธีที่ดีกว่าในการดำเนินการต่อไปนี้หรือไม่:
ฉันต้องการตรวจสอบค่าว่างเพื่อให้เกิดขึ้นในไฟล์ส่วนหัวก่อนดำเนินการต่อกับลูป

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

ในระยะสั้นมันดูน่าเกลียดเล็กน้อยที่จะเขียน foreach ภายใน if เนื่องจากระดับการเยื้องที่เกิดขึ้นในโค้ดของฉัน

เป็นสิ่งที่น่าจะประเมินได้

foreach(var h in (file.Headers != null))
{
  //do stuff
}

เป็นไปได้?


3
คุณสามารถดูได้ที่นี่: stackoverflow.com/questions/6937407/…
Adrian Fâciu

stackoverflow.com/questions/872323/…เป็นอีกหนึ่งไอเดีย
weismat

1
@AdrianFaciu ฉันคิดว่ามันแตกต่างอย่างสิ้นเชิง คำถามจะตรวจสอบว่าคอลเลกชันเป็นโมฆะก่อนที่จะทำ for-each ลิงก์ของคุณตรวจสอบว่ารายการในคอลเล็กชันเป็นโมฆะหรือไม่
rikitikitik


1
C # 8 สามารถมี foreach ที่มีเงื่อนไขเป็นโมฆะได้เช่นไวยากรณ์เช่นนี้ foreach? (var i in collection) {} ฉันคิดว่ามันเป็นสถานการณ์ที่ธรรมดาพอที่จะพิสูจน์เรื่องนี้ได้และจากการเพิ่มเงื่อนไขที่เป็นโมฆะเมื่อเร็ว ๆ นี้ในภาษามันก็สมเหตุสมผลแล้วสำหรับ?
mms

คำตอบ:


126

เช่นเดียวกับการเสริมความงามเล็กน้อยจากคำแนะนำของ Rune คุณสามารถสร้างวิธีการขยายของคุณเองได้:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

จากนั้นคุณสามารถเขียน:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

เปลี่ยนชื่อตามรสนิยม :)


80

สมมติว่าประเภทขององค์ประกอบในไฟล์ส่วนหัวคือ T คุณสามารถทำได้

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

สิ่งนี้จะสร้างการแจกแจง T ถ้าไฟล์ว่างส่วนหัวเป็นโมฆะ หากประเภทของไฟล์เป็นประเภทที่คุณเป็นเจ้าของฉันจะพิจารณาเปลี่ยน getter Headersแทน nullคือค่าที่ไม่รู้จักดังนั้นถ้าเป็นไปได้แทนที่จะใช้ null เป็น "ฉันรู้ว่าไม่มีองค์ประกอบ" เมื่อ null จริง (/ เดิม) ควรตีความว่า "ฉันไม่รู้ว่ามีองค์ประกอบใดบ้าง" ให้ใช้ชุดว่างเพื่อแสดง ที่คุณรู้ว่าไม่มีองค์ประกอบในชุด นั่นก็จะแห้งกว่าเช่นกันเนื่องจากคุณไม่ต้องทำการตรวจสอบค่าว่างบ่อยเท่า

แก้ไขตามข้อเสนอแนะของ Jons คุณยังสามารถสร้างวิธีการขยายโดยเปลี่ยนรหัสด้านบนเป็น

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

ในกรณีที่คุณไม่สามารถเปลี่ยน getter ได้นี่เป็นที่ต้องการของฉันเองเนื่องจากเป็นการแสดงเจตนาที่ชัดเจนยิ่งขึ้นโดยตั้งชื่อการดำเนินการ (OrEmptyIfNull)

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

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}

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

@Tom นอกเหนือจากการตรวจสอบโมฆะหนึ่งครั้งไม่มีบทลงโทษสำหรับกรณีที่ตัวแจงนับไม่เป็นโมฆะ การหลีกเลี่ยงการตรวจสอบนั้นในขณะเดียวกันก็ทำให้แน่ใจว่าตัวนับไม่เป็นโมฆะนั้นเป็นไปไม่ได้ โค้ดด้านบนกำหนดให้ส่วนหัวเป็นส่วนIEnumerable ที่มีข้อ จำกัด มากกว่าforeachข้อกำหนด แต่มีข้อ จำกัด น้อยกว่าข้อกำหนดList<T>ในคำตอบที่คุณเชื่อมโยง ซึ่งมีโทษประสิทธิภาพเดียวกันในการทดสอบว่า enumerable เป็นโมฆะหรือไม่
Rune FS

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

@tom หลักฐานของคำตอบคือไฟล์นั้นส่วนหัวเป็น IEnumerable <T> ซึ่งในกรณีนี้คอมไพลเลอร์ไม่สามารถทำการปรับให้เหมาะสมได้ อย่างไรก็ตามมันค่อนข้างตรงไปตรงมาที่จะขยายโซลูชันวิธีการขยายเพื่อหลีกเลี่ยงปัญหานี้ ดูแก้ไข
Rune FS

19

ตรงไปตรงมาฉันแนะนำ: เพียงแค่ดูดการnullทดสอบ nullทดสอบเพียงbrfalseหรือbrfalse.s; ทุกสิ่งทุกอย่างจะไปเกี่ยวข้องกับการทำงานมากขึ้น (การทดสอบการกำหนดวิธีการโทรพิเศษที่ไม่จำเป็นGetEnumerator(), MoveNext(), Dispose()ใน iterator ฯลฯ )

การifทดสอบนั้นง่ายชัดเจนและมีประสิทธิภาพ


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

3
เพียงแค่ทราบสั้น ๆ เกี่ยวกับ Marc นี้ .. หลายปีหลังจากที่ฉันถามคำถามนี้และจำเป็นต้องใช้การปรับปรุงประสิทธิภาพบางอย่างคำแนะนำของคุณก็มีประโยชน์อย่างยิ่ง ขอบคุณ
Eminem

13

"if" ก่อนที่จะทำการวนซ้ำก็ใช้ได้ความหมายที่ "สวย" เพียงไม่กี่อย่างสามารถทำให้โค้ดของคุณอ่านได้น้อยลง

อย่างไรก็ตามหากการเยื้องรบกวนคุณคุณสามารถเปลี่ยน if เพื่อตรวจสอบ:

if(file.Headers == null)  
   return;

และคุณจะไปที่ foreach loop ก็ต่อเมื่อมีค่าจริงที่คุณสมบัติส่วนหัว

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

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(แทนที่คอลเล็กชันด้วยวัตถุ / ประเภทที่แท้จริงของคุณ)


ตัวเลือกแรกของคุณจะไม่ทำงานหากมีรหัสใด ๆ อยู่นอกคำสั่ง if
rikitikitik

ฉันยอมรับว่าคำสั่ง if นั้นง่ายและราคาถูกในการนำไปใช้เมื่อเทียบกับการสร้างรายการใหม่เพียงเพื่อการตกแต่งโค้ด
Andreas Johansson

11

ใช้Null-conditional Operatorและ ForEach () ซึ่งทำงานได้เร็วกว่า foreach loop มาตรฐาน
คุณต้องส่งคอลเลกชันไปที่รายการแม้ว่า

   listOfItems?.ForEach(item => // ... );

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

ทางออกที่ดีที่สุดสำหรับกรณีของฉัน
Josef Henn

3

ฉันใช้วิธีการขยายเล็ก ๆ น้อย ๆ ที่ดีสำหรับสถานการณ์เหล่านี้:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

เนื่องจากส่วนหัวเป็นรายการประเภทคุณสามารถทำสิ่งต่อไปนี้:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}

1
คุณสามารถใช้ตัว??ดำเนินการและย่อคำสั่ง return เป็นreturn list ?? new List<T>;
Rune FS

1
@wolfgangziegler ถ้าฉันเข้าใจถูกต้องการทดสอบnullในตัวอย่างของคุณfile.Headers.EnsureNotNull() != nullไม่จำเป็นและผิดด้วยซ้ำ?
Remko Jansen

0

ในบางกรณีฉันต้องการตัวแปรทั่วไปอื่นเล็กน้อยโดยสมมติว่าตามกฎแล้วตัวสร้างคอลเล็กชันเริ่มต้นจะส่งคืนอินสแตนซ์ว่าง

จะดีกว่าถ้าตั้งชื่อวิธีNewIfDefaultนี้ อาจมีประโยชน์ไม่เพียง แต่สำหรับคอลเลกชันดังนั้นข้อ จำกัด ประเภทIEnumerable<T>อาจซ้ำซ้อน

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.