การปรับปรุง C # foreach?


9

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

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


7
คุณสามารถทำได้ด้วยตัวเองด้วยวิธีการต่อเติมดูคำตอบจาก Dan Finch: stackoverflow.com/a/521894/866172 (ไม่ใช่คำตอบที่ได้คะแนนดีที่สุด แต่ฉันคิดว่ามันเป็น "ตัวทำความสะอาด")
Jalayn

Perl และสิ่งต่าง ๆ เช่น $ _ อยู่ในใจ ฉันเกลียดสิ่งนั้น
Yam Marcovic

2
จากสิ่งที่ฉันรู้เกี่ยวกับ C # มันมีคำหลักตามบริบทจำนวนมากดังนั้นสิ่งนี้จะไม่ "ขัดกับการใช้คำหลัก" ตามที่ระบุไว้ในคำตอบด้านล่างอย่างไรก็ตามคุณอาจต้องการใช้การfor () วนซ้ำ
Craige

@Jalayn กรุณาโพสต์ว่าเป็นคำตอบ มันเป็นหนึ่งในแนวทางที่ดีที่สุด
Apoorv Khurasia

@ MonsterTruck: ฉันไม่คิดว่าเขาควรจะ ทางออกที่แท้จริงคือการโยกย้ายคำถามนี้ไปยัง SO และปิดเป็นซ้ำกับคำถามที่เชื่อมโยง Jalayn
Brian

คำตอบ:


54

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


1
+1 - จุดดี มันง่ายพอที่จะใช้ IEnumerable.Count หรือ object.length กับลูปปกติเพื่อหลีกเลี่ยงปัญหาใด ๆ กับการหาจำนวนองค์ประกอบในวัตถุ

11
@ GlenH7: ขึ้นอยู่กับการใช้งานIEnumerable.Countอาจมีราคาแพง (หรือเป็นไปไม่ได้หากเป็นเพียงการแจงนับครั้งเดียว) คุณต้องรู้ว่าแหล่งข้อมูลพื้นฐานคืออะไรก่อนที่คุณจะทำอย่างปลอดภัย
ไบนารี Worrier

2
-1: บิลแว็กเนอร์ในที่มีประสิทธิภาพมากกว่า C #จะอธิบายว่าทำไมหนึ่งควรติดมากกว่าforeach for (int i = 0…)เขาเขียนสองสามหน้า แต่ฉันสามารถสรุปสองคำ - การเพิ่มประสิทธิภาพคอมไพเลอร์ในการทำซ้ำ ตกลงนั่นไม่ใช่สองคำ
Apoorv Khurasia

13
@ MonsterTruck: สิ่งที่ Bill Wagner เขียนฉันได้เห็นสถานการณ์มากพอ ๆ กับที่ OP อธิบายซึ่งforลูปจะให้โค้ดที่อ่านได้ดีกว่า
Doc Brown

1
@DocBrown สิ่งนี้ไม่ง่ายอย่างการเพิ่มประสิทธิภาพก่อนวัยอันควร ฉันเคยระบุคอขวดในอดีตและแก้ไขโดยใช้วิธีการนี้ (แต่ฉันจะยอมรับว่าฉันกำลังจัดการกับวัตถุนับพันในคอลเลกชัน) หวังว่าจะโพสต์ตัวอย่างการทำงานในไม่ช้า ในขณะเดียวกันนี่คือจอนสกีต [เขาบอกว่าการปรับปรุงประสิทธิภาพจะไม่สำคัญ แต่ขึ้นอยู่กับการสะสมและจำนวนวัตถุ]
Apoorv Khurasia

37

คุณสามารถใช้ประเภทที่ไม่ระบุชื่อเช่นนี้

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

คล้ายกับenumerateฟังก์ชั่นของ Python

for i, v in enumerate(items):
    print v, i

+1 โอ้ ... หลอดไฟ ฉันชอบวิธีการนั้น ทำไมฉันไม่เคยคิดมาก่อน
Phil

17
ใครก็ตามที่ชอบสิ่งนี้ใน C # มากกว่าคลาสสิกสำหรับลูปอย่างชัดเจนมีความคิดเห็นแปลก ๆ เกี่ยวกับการอ่าน นี่อาจเป็นกลลวง แต่ฉันจะไม่ยอมให้เป็นอย่างนั้นในรหัสคุณภาพการผลิต มันเป็นเสียงที่ดังกว่าคลาสสิกสำหรับวงและไม่สามารถเข้าใจได้อย่างรวดเร็ว
เหยี่ยว

4
@ เหยี่ยวนี่เป็นทางเลือกที่ถูกต้องหากคุณไม่สามารถใช้แฟชั่นแบบเก่าสำหรับลูป (ซึ่งต้องการการนับ) นั่นเป็นคอลเล็กชันของวัตถุสองสามชิ้นที่ฉันต้องแก้ไข ...
Fabricio Araujo

8
@FabricioAraujo: แน่นอน C # สำหรับลูปไม่จำเป็นต้องมีการนับ เท่าที่ฉันรู้ว่ามันเหมือนกับ C สำหรับลูปซึ่งหมายความว่าคุณสามารถใช้ค่าบูลีนเพื่อจบลูป เช่นการตรวจสอบการสิ้นสุดของการแจงนับเมื่อ MoveNext ส่งคืนเท็จ
Zan Lynx

1
นี่เป็นข้อดีเพิ่มเติมของการมีรหัสทั้งหมดเพื่อจัดการกับการเพิ่มขึ้นในช่วงเริ่มต้นของบล็อก foreach แทนที่จะต้องค้นหามัน
JeffO

13

ฉันแค่เพิ่มคำตอบของ Dan Finchที่นี่ตามที่ร้องขอ

อย่าให้คะแนนกับฉันให้คะแนนกับ Dan Finch :-)

การแก้ปัญหาคือการเขียนวิธีการขยายทั่วไปต่อไปนี้:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

ซึ่งใช้เช่น:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
มันค่อนข้างฉลาด แต่ฉันคิดว่าการใช้งานแบบปกติสำหรับนั้น "อ่านง่าย" ได้มากกว่าโดยเฉพาะอย่างยิ่งถ้าถนนคนที่ลงเอยด้วยการบำรุงรักษาโค้ดอาจไม่เก่งมากใน. NET และไม่คุ้นเคยกับแนวคิดของวิธีการขยาย . ฉันยังคงคว้าโค้ดเล็ก ๆ น้อย ๆ นี้ :)
MetalMikester

1
สามารถโต้เถียงทั้งสองด้านนี้และคำถามเดิม - ฉันเห็นด้วยกับผู้โพสต์เจตนามีหลายกรณีที่ foreach อ่านได้ดีกว่า แต่ต้องการดัชนี แต่ฉันก็มักจะเพิ่มน้ำตาล syntactic ให้เป็นภาษาถ้าไม่ใช่ จำเป็นอย่างยิ่ง - ฉันมีวิธีการแก้ปัญหาเดียวกันในรหัสของฉันเองที่มีชื่อแตกต่างกัน - strings.ApplyIndexed <T> (...... )
Mark Mullin

ฉันเห็นด้วยกับ Mark Mullin คุณสามารถโต้เถียงทั้งสองด้าน ฉันคิดว่ามันเป็นการใช้วิธีการขยายที่ชาญฉลาดและฉันก็ติดตั้งคำถามดังนั้นฉันจึงแบ่งปันมัน
Jalayn

5

ฉันอาจจะผิดเกี่ยวกับเรื่องนี้ แต่ฉันคิดเสมอว่าจุดหลักของการวนรอบ foreach คือการทำซ้ำง่ายจาก STL วันของ C ++ คือ

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

เปรียบเทียบสิ่งนี้กับ. NET การใช้ IEnumerable ใน foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

หลังนั้นง่ายกว่ามากและหลีกเลี่ยงหลุมพรางเก่า ๆ นอกจากนี้พวกเขาดูเหมือนจะปฏิบัติตามกฎเกือบเหมือนกัน ตัวอย่างเช่นคุณไม่สามารถเปลี่ยนเนื้อหา IEnumerable ในขณะที่อยู่ในลูป (ซึ่งทำให้รู้สึกมากขึ้นถ้าคุณคิดว่ามันเป็นวิธี STL) อย่างไรก็ตามการวนซ้ำมักจะมีวัตถุประสงค์เชิงตรรกะที่แตกต่างจากการวนซ้ำ


4
+1: มีน้อยคนที่จำได้ว่า foreach นั้นเป็นประโยคน้ำตาลตามแบบตัววนซ้ำ
Steven Evers

1
มีความแตกต่างอยู่บ้าง การจัดการตัวชี้ที่มีอยู่ใน. NET ถูกซ่อนอยู่ค่อนข้างลึกจากโปรแกรมเมอร์ แต่คุณสามารถเขียนลูปสำหรับลูปที่ดูคล้ายกับตัวอย่างของซีพลัสพลัส:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS ไม่ใช่ว่าคุณรู้ว่าทุกสิ่งที่คุณสัมผัสใน. NET นั้นไม่ใช่ประเภทที่มีโครงสร้างเป็นตัวชี้เพียงแค่มีไวยากรณ์ที่แตกต่างกัน (. แทน ->) ในความเป็นจริงการส่งประเภทการอ้างอิงไปยังเมธอดจะเหมือนกับการส่งผ่านตัวชี้และการส่งผ่านโดยการอ้างอิงนั้นจะเป็นการส่งผ่านเป็นตัวชี้ดับเบิล สิ่งเดียวกัน อย่างน้อยนั่นก็เป็นวิธีที่บุคคลที่มีภาษาพื้นเมืองคือ C ++ คิดเกี่ยวกับมัน เรียงลำดับเช่นคุณเรียนภาษาสเปนในโรงเรียนโดยคิดว่าภาษามีความสัมพันธ์กับภาษาของคุณอย่างไร
Jonathan Henson

* ค่าประเภทไม่ใช่แบบโครงสร้าง ขอโทษ
Jonathan Henson

2

หากการรวบรวมที่เป็นปัญหาIList<T>คุณสามารถใช้forกับการจัดทำดัชนีได้:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

ถ้าไม่เช่นนั้นคุณสามารถใช้Select()มันมีโอเวอร์โหลดที่ให้ดัชนีกับคุณ

หากนั่นยังไม่เหมาะสมฉันคิดว่าการรักษาดัชนีด้วยตนเองนั้นง่ายพอมันเป็นเพียงโค้ดสั้น ๆ สองบรรทัด การสร้างคำหลักเฉพาะสำหรับสิ่งนี้จะเป็นการ overkill

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

+1 เช่นกันหากคุณใช้ดัชนีหนึ่งครั้งภายในวงคุณสามารถทำสิ่งที่ต้องการfoo(++i)หรือfoo(i++)ขึ้นอยู่กับว่าต้องการดัชนีใด
งาน

2

หากคุณรู้ว่าคอลเล็กชันนั้นมีจำนวน IEnumerable แต่จำเป็นต้องติดตามจำนวนองค์ประกอบที่คุณประมวลผลจนถึงตอนนี้ (และเมื่อรวมทั้งหมดเมื่อคุณทำเสร็จแล้ว) คุณสามารถเพิ่มสองบรรทัดเป็นพื้นฐานสำหรับลูป:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

คุณยังสามารถเพิ่มตัวแปรดัชนีแบบเพิ่มหน่วยลงใน foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

หากคุณต้องการทำให้ลุคนี้ดูหรูหรายิ่งขึ้นคุณสามารถกำหนดวิธีการขยายหรือสองเพื่อ "ซ่อน" รายละเอียดเหล่านี้:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

การกำหนดขอบเขตยังใช้งานได้หากคุณต้องการรักษาความสะอาดของการชนด้วยชื่ออื่น ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

สำหรับเรื่องนี้ฉันใช้whileวง ลูปที่มีการใช้forงานด้วยและหลาย ๆ คนดูเหมือนจะชอบพวกเขา แต่ฉันชอบที่จะใช้forกับบางสิ่งที่จะมีจำนวนการทำซ้ำที่แน่นอน IEnumerables อาจถูกสร้างขึ้นในเวลาทำงานดังนั้นในความคิดของฉันwhileทำให้มีเหตุผลมากขึ้น เรียกว่าเป็นแนวทางการเข้ารหัสอย่างไม่เป็นทางการหากคุณต้องการ

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