คุณจะรับดัชนีการวนซ้ำปัจจุบันของ foreach loop ได้อย่างไร


938

มีการสร้างภาษาที่หายากที่ฉันไม่ได้พบ (เช่นไม่กี่ที่ฉันได้เรียนรู้เมื่อเร็ว ๆ นี้บางภาษาใน Stack Overflow) ใน C # เพื่อรับค่าที่แสดงถึงการวนซ้ำปัจจุบันของ foreach loop หรือไม่?

ตัวอย่างเช่นฉันกำลังทำสิ่งนี้ขึ้นอยู่กับสถานการณ์:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

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

8
ฉันจะบอกว่าวัตถุประสงค์หลักของforeachคือการให้กลไกการทำซ้ำทั่วไปสำหรับคอลเลกชันทั้งหมดโดยไม่คำนึงว่าพวกเขาจะสามารถจัดทำดัชนี ( List) หรือไม่ ( Dictionary)
Brian Gideon

2
สวัสดี Brian Gideon - เห็นด้วยอย่างแน่นอน (เมื่อไม่กี่ปีที่ผ่านมาและตอนนี้ฉันก็ไม่ค่อยมีประสบการณ์เท่าไหร่) อย่างไรก็ตามในขณะที่Dictionaryไม่สามารถจัดทำดัชนีได้ แต่การวนซ้ำของDictionaryจะข้ามไปตามลำดับเฉพาะ (เช่นตัวระบุจะสามารถจัดทำดัชนีได้ตามความเป็นจริงซึ่งให้องค์ประกอบตามลำดับ) ในแง่นี้เราสามารถพูดได้ว่าเราไม่ได้มองหาดัชนีภายในคอลเล็กชัน แต่เป็นดัชนีขององค์ประกอบที่แจกแจงปัจจุบันภายในการแจงนับ
Matt Mitchell

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

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

คำตอบ:


551

foreachสำหรับ iterating IEnumerableกว่าคอลเลกชันที่ใช้ ทำได้โดยการโทรGetEnumeratorไปที่คอลเล็กชันซึ่งจะส่งคืนEnumeratorในคอลเลกชันที่จะกลับ

ตัวแจงนับนี้มีวิธีการและคุณสมบัติ:

  • MoveNext ()
  • ปัจจุบัน

Currentส่งคืนวัตถุที่ Enumerator เปิดอยู่MoveNextอัปเดตCurrentเป็นวัตถุถัดไป

แนวคิดของดัชนีแตกต่างจากแนวคิดของการแจงนับและไม่สามารถทำได้

ด้วยเหตุนี้คอลเลกชันส่วนใหญ่จึงสามารถผ่านได้โดยใช้ตัวทำดัชนีและโครงสร้างลูป

ฉันชอบใช้ลูป for ในสถานการณ์นี้มากเมื่อเทียบกับการติดตามดัชนีด้วยตัวแปรโลคอล


165
"เห็นได้ชัดว่าแนวคิดของดัชนีต่างกับแนวคิดของการแจงนับและไม่สามารถทำได้" - นี่เป็นเรื่องไร้สาระเนื่องจากคำตอบของ David B และ bcahill นั้นชัดเจน ดัชนีเป็นการแจงนับช่วงและไม่มีเหตุผลที่ไม่สามารถแจกแจงสองสิ่งในแบบคู่ขนาน ... นั่นคือสิ่งที่รูปแบบการจัดทำดัชนีของ Enumerable เลือกได้
Jim Balter

11
ตัวอย่างรหัสพื้นฐาน:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock

22
@JimBalter: นั่นคือลิงค์แรกที่ฉันอ่าน ฉันไม่เห็นว่ามันสนับสนุนตำแหน่งของคุณอย่างไร ฉันยังไม่ขว้างโฆษณา ad-homs และคำแตกแยกที่ผู้คนเมื่อตอบกลับ ฉันอ้างอิงเป็นตัวอย่างการใช้งานของคุณ "ไร้สาระ", "ความสับสนพิเศษ", "ผิดอย่างสมบูรณ์และสับสน", "ฉันมี 37 upvotes" ฯลฯ (ฉันคิดว่าอัตตาของคุณอยู่บนเส้นตรงนี้) แทนฉัน ต้องการที่จะถามอย่างสุภาพว่าคุณไม่ได้เรียกร้องให้ผู้อื่นใช้ 'อาร์กิวเมนต์ ad verecundiam' ของคุณและฉันอยากแนะนำให้คุณคิดหาวิธีที่จะช่วยเหลือผู้คนที่นี่บน StackOverflow ฉันจะบอกว่า Jon Skeet เป็นแบบอย่างที่นี่ในเรื่องนั้น
Pretzel

11
เพรทเซล: การใช้ Jon Skeet ของคุณในฐานะพลเมืองตัวอย่างผิดพลาดในขณะที่เขาจะเรียกดู (และ downvote) คนที่ไม่เห็นด้วยกับเขาอย่างที่ฉันมีประสบการณ์ Jim Balter: ความคิดเห็นบางส่วนของคุณก้าวร้าวมากเกินไปและคุณสามารถแสดงความคิดเห็นของคุณด้วยความโกรธน้อยลงและการศึกษามากขึ้น
Suncat2000

7
@Pretzel จุดของ Jim คือตราบใดที่คุณสามารถแมปองค์ประกอบเข้ากับลำดับของจำนวนเต็มคุณสามารถสร้างดัชนีได้ ที่ชั้นเรียนไม่ได้จัดเก็บดัชนีอยู่ด้านข้างจุด นอกจากนี้รายการที่เชื่อมโยงจะมีคำสั่งซื้อเท่านั้นที่ทำให้ตำแหน่งของจิมแข็งแกร่งขึ้น สิ่งที่คุณต้องทำคือการเรียงลำดับองค์ประกอบตามลำดับ โดยเฉพาะคุณสามารถทำสิ่งนี้ได้โดยการเพิ่มจำนวนในขณะที่คุณทำซ้ำหรือคุณสามารถสร้างรายการจำนวนเต็มที่มีความยาวเท่ากันจากนั้นทำการซิปพวกมัน (เหมือนในzipฟังก์ชั่นของ Python )
jpmc26

666

Ian Mercer โพสต์โซลูชันที่คล้ายกันเช่นนี้ในบล็อกของ Phil Haack :

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

นี้ทำให้คุณได้รับรายการ ( item.value) และดัชนี ( item.i) โดยใช้เกินพิกัดของ LINQ นี่Select :

พารามิเตอร์ที่สองของฟังก์ชั่น [ภายในเลือก] หมายถึงดัชนีขององค์ประกอบที่มา

new { i, value }คือการสร้างใหม่วัตถุที่ไม่ระบุชื่อ

การจัดสรรฮีปสามารถหลีกเลี่ยงได้โดยใช้ValueTupleหากคุณใช้ C # 7.0 หรือใหม่กว่า:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

คุณยังสามารถกำจัดitem.โดยใช้การทำลายอัตโนมัติ:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

9
วิธีแก้ปัญหานั้นดีสำหรับกรณีของเทมเพลตมีดโกนที่ความประณีตของเทมเพลตเป็นเรื่องที่ไม่เกี่ยวข้องกับการออกแบบและคุณต้องการใช้ดัชนีของทุกรายการที่แจกแจง อย่างไรก็ตามโปรดจำไว้ว่าการจัดสรรวัตถุจาก 'การตัด' เพิ่มค่าใช้จ่าย (ในอวกาศและเวลา) ที่ด้านบนของการเพิ่ม (ไม่สามารถหลีกเลี่ยงได้) ของจำนวนเต็ม
David Bullock

6
@mjsr เอกสารเป็นที่นี่
Thorkil Holm-Jacobsen

19
ขออภัย - มันฉลาด แต่จริงๆแล้วมันสามารถอ่านได้มากกว่าการสร้างดัชนีนอก foreach และเพิ่มมันแต่ละวง?
jbyrd

13
ด้วยเวอร์ชัน C # ในภายหลังดังนั้นคุณจึงใช้สิ่งอันดับดังนั้นคุณจะมีสิ่งนี้: foreach (var (item, i) ใน Model.Select ((v, i) => (v, i)) ช่วยให้คุณ เข้าถึงไอเท็มและดัชนี (i) โดยตรงภายใน for-loop ด้วย tuple deconstruction
Haukman

5
บางคนสามารถอธิบายให้ฉันฟังได้ว่าทำไมคำตอบนี้จึงเป็นคำตอบที่ดี (มากกว่า 450 upvotes ณ เวลาที่เขียน) เท่าที่ฉันเห็นว่ามันยากที่จะเข้าใจมากกว่าการเพิ่มตัวนับเพียงอย่างเดียวดังนั้นจึงสามารถบำรุงรักษาได้น้อยกว่าใช้หน่วยความจำมากขึ้นและอาจช้ากว่า ฉันพลาดอะไรไปรึเปล่า?
Rich N

182

ในที่สุด C # 7 มีไวยากรณ์ที่ดีสำหรับการสร้างดัชนีภายในforeachลูป (เช่น tuples):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

ต้องการวิธีการขยายเล็กน้อย:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

8
คำตอบนี้ถูกประเมินต่ำกว่าปกติการมีสิ่งอันดับจะสะอาดกว่าเดิม
ทอดด์

7
แก้ไขเพื่อจัดการคอลเลกชัน null:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
โหลด

ทำได้ดีนี่. ฉันชอบโซลูชันนี้มากที่สุด
FranzHuber23

นี่คือคำตอบที่ดีที่สุด
w0ns88

2
มันอาจมีประโยชน์ในการเรียกใช้เมธอดEnumeratedให้เป็นที่รู้จักมากขึ้นสำหรับคนที่ใช้ภาษาอื่น (และอาจสลับลำดับของพารามิเตอร์ tuple ด้วย) ไม่ว่าWithIndexจะไม่ชัดเจน
FernAndr

114

สามารถทำสิ่งนี้:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

12
แต่นั่นไม่ได้ "แก้ปัญหา" จริงๆ ความคิดนั้นดี แต่ก็ไม่ได้หลีกเลี่ยงตัวแปรการนับเพิ่มเติม
Atmocreations

สิ่งนี้ไม่ได้ผลถ้าเรามีคำสั่ง return ภายในวงวนของเราหากคุณเปลี่ยน "ForEachWithIndex" สำหรับเรื่องนั้นมันไม่ธรรมดาเลยดีกว่าถ้าเขียนแบบปกติสำหรับ loop
Shankar Raju

การเรียก ForEachWithIndex ของคุณเทียบเท่ากับการเรียกใช้ Linq Select ที่รับสตริงและดัชนี:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

ฉันไม่เห็นด้วยกับความคิดเห็นที่forวงเป็นตัวเลือกที่ดีกว่าในกรณีส่วนใหญ่

foreach เป็นโครงสร้างที่มีประโยชน์และไม่สามารถแทนที่ได้โดย forวนซ้ำในทุกสถานการณ์

ตัวอย่างเช่นถ้าคุณมีDataReaderและวนรอบระเบียนทั้งหมดโดยใช้การforeachเรียกอัตโนมัติทิ้งวิธีการและปิดเครื่องอ่าน (ซึ่งจะปิดการเชื่อมต่อโดยอัตโนมัติ) ดังนั้นจึงปลอดภัยกว่าเพราะจะช่วยป้องกันการรั่วไหลของการเชื่อมต่อแม้ว่าคุณจะลืมปิดเครื่องอ่าน

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

อาจมีตัวอย่างอื่น ๆ ของการโทรโดยนัยของDisposeวิธีการที่เป็นประโยชน์


2
ขอบคุณที่ชี้นำสิ่งนี้ ค่อนข้างบอบบาง คุณจะได้รับข้อมูลเพิ่มเติมได้ที่pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumeratorและmsdn.microsoft.com/en-us/library/aa664754(VS.71).aspx
Mark Meuer

+1 ผมเขียนเพิ่มเติมในรายละเอียดเกี่ยวกับวิธีการforeachจะแตกต่างจากfor(และใกล้ชิดกับwhile) บนProgrammers.SE
Arseni Mourzenko

64

คำตอบที่แท้จริง - คำเตือนประสิทธิภาพอาจไม่ดีเท่าที่ใช้เพียงintเพื่อติดตามดัชนี อย่างน้อยมันก็ดีกว่าการใช้IndexOfอย่างน้อยก็เป็นดีกว่าการใช้

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

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

3
เหตุผลเดียวที่จะใช้ OfType <T> () แทน Cast <T> () คือหากบางรายการในการแจงนับอาจล้มเหลวการส่งแบบชัดแจ้ง สำหรับวัตถุสิ่งนี้จะไม่เกิดขึ้น
dahlbyk

13
แน่นอนยกเว้นเหตุผลอื่นที่จะใช้ OfType แทน Cast - ซึ่งก็คือฉันไม่เคยใช้ Cast
Amy B

36

การใช้ LINQ, C # 7 และSystem.ValueTupleแพ็คเกจ NuGet คุณสามารถทำได้:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

คุณสามารถใช้โครงสร้างปกติforeachและสามารถเข้าถึงค่าและดัชนีโดยตรงไม่ใช่สมาชิกของวัตถุและเก็บทั้งสองฟิลด์ไว้ในขอบเขตของลูปเท่านั้น ด้วยเหตุผลเหล่านี้ผมเชื่อว่านี่เป็นทางออกที่ดีที่สุดถ้าคุณสามารถที่จะใช้ C # 7 System.ValueTupleและ


สิ่งนี้แตกต่างจากคำตอบของผู้ใช้ 1414213562หรือไม่
Edward Brey

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

1
สิ่งนี้แตกต่างกันเนื่องจาก. Select ถูกติดตั้งมาจาก LINQ คุณไม่ต้องเขียนฟังก์ชั่นของคุณเองเหรอ? คุณจะต้องมี VS ติดตั้ง "System.ValueTuple" แม้ว่า
Anton

33

ใช้คำตอบของ @ FlySwat ฉันคิดวิธีนี้:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

คุณได้รับ enumerator ที่ใช้GetEnumeratorและจากนั้นคุณวนซ้ำโดยใช้การforวนซ้ำ listEnumerator.MoveNext() == trueแต่เคล็ดลับคือการทำให้สภาพ loop ของ

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


10
ไม่จำเป็นต้องเปรียบเทียบ listEnumerator.MoveNext () == จริง มันเหมือนกับถามคอมพิวเตอร์ว่าจริง == จริงไหม :) เพียงแค่พูดว่า listEnumerator.MoveNext () {}
โปรดคลิกที่นี่

9
@Zesty คุณถูกต้องอย่างแน่นอน ฉันรู้สึกว่าสามารถเพิ่มได้ในกรณีนี้โดยเฉพาะอย่างยิ่งสำหรับผู้ที่ไม่คุ้นเคยกับการป้อนสิ่งอื่นนอกเหนือจาก i <blahSize เป็นเงื่อนไข
Gezim

@Gezim ฉันไม่รังเกียจเพรดิเคตที่นี่ แต่ฉันได้รับจุดของคุณในสิ่งที่ไม่ดูเหมือนภาคแสดง
John Dvorak

1
คุณควรกำจัดตัวแจงนับ
Antonín Lejsek

@ IDisposableAntonínLejsekแจงนับไม่ใช้
Edward Brey

27

ไม่มีอะไรผิดปกติกับการใช้ตัวแปรตัวนับ ในความเป็นจริงไม่ว่าคุณจะใช้for, foreach whileหรือdoตัวแปรเคาน์เตอร์ต้องอยู่ที่ไหนสักแห่งได้รับการประกาศและการเพิ่มขึ้น

ดังนั้นให้ใช้สำนวนนี้ถ้าคุณไม่แน่ใจว่าคุณมีคอลเลกชันที่จัดทำดัชนีไว้อย่างเหมาะสมหรือไม่:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

ใช้สิ่งนี้หากคุณรู้ว่าคอลเลกชันที่จัดทำดัชนีของคุณคือ O (1) สำหรับการเข้าถึงดัชนี (ซึ่งจะเป็นArrayและสำหรับList<T>(เอกสารไม่ได้พูด) แต่ไม่จำเป็นสำหรับประเภทอื่น ๆ (เช่นLinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

ไม่จำเป็นต้อง 'ดำเนินการด้วยตนเอง' IEnumeratorโดยการเรียกใช้MoveNext()และสอบปากคำCurrent- foreachช่วยให้คุณรำคาญโดยเฉพาะ ... หากคุณต้องการข้ามรายการเพียงใช้ a continueในร่างกายของลูป

และเพื่อความสมบูรณ์ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณทำกับดัชนีของคุณ (โครงสร้างด้านบนมีความยืดหยุ่นมากมาย) คุณอาจใช้ Parallel LINQ:

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

เราใช้AsParallel()ข้างต้นเพราะมันเป็นปี 2014 แล้วและเราต้องการใช้ประโยชน์จากหลาย ๆ คอร์เหล่านั้นเพื่อเร่งความเร็วของสิ่งต่างๆ นอกจากนี้สำหรับ LINQ แบบ 'ต่อเนื่อง' คุณจะได้รับForEach()วิธีการขยายList<T>และArray ... และยังไม่ชัดเจนว่าการใช้มันจะดีกว่าการทำแบบง่าย ๆforeachเนื่องจากคุณยังคงใช้เธรดเดี่ยวสำหรับไวยากรณ์ที่น่าเกลียด


26

คุณสามารถล้อมรอบตัวแจงนับดั้งเดิมด้วยตัวอื่นที่มีข้อมูลดัชนี

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

นี่คือรหัสสำหรับForEachHelperชั้นเรียน

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

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

7
@ Lucas: ไม่ แต่มันจะคืนค่าดัชนีของการวนซ้ำล่วงหน้าปัจจุบัน นั่นคือคำถาม
Brian Gideon

20

นี่เป็นวิธีแก้ปัญหาที่ฉันเพิ่งพบสำหรับปัญหานี้

รหัสเดิม:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

อัปเดตรหัสแล้ว

enumerable.ForEach((item, index) => blah(item, index));

วิธีการขยาย:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

16

เพียงเพิ่มดัชนีของคุณเอง ง่าย ๆ เข้าไว้.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

15

มันจะทำงานให้กับรายการเท่านั้นไม่ใช่ IEnumerable ใด ๆ แต่ใน LINQ มีสิ่งนี้:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@ Jonathan ฉันไม่ได้บอกว่ามันเป็นคำตอบที่ดีฉันแค่บอกว่ามันแค่แสดงให้เห็นว่ามันเป็นไปได้ที่จะทำในสิ่งที่เขาถาม :)

@Graphain ฉันไม่คิดว่ามันจะเร็ว - ฉันไม่แน่ใจทั้งหมดว่ามันทำงานอย่างไรมันอาจจะย้ำผ่านรายการทั้งหมดในแต่ละครั้งเพื่อหาวัตถุที่ตรงกันซึ่งจะเป็นสิ่งที่เปรียบเทียบได้

ที่กล่าวว่ารายการอาจเก็บดัชนีของแต่ละวัตถุพร้อมกับการนับ

ดูเหมือนว่าโจนาธานจะมีความคิดที่ดีกว่านี้

มันจะเป็นการดีกว่าถ้าคุณนับจำนวนที่คุณคิดว่าอยู่ข้างหน้าเรียบง่ายและปรับเปลี่ยนได้มากกว่า


5
ไม่แน่ใจในการดาวน์โหลดที่หนักหน่วง ประสิทธิภาพที่แน่นอนทำให้สิ่งต้องห้ามนี้ แต่คุณตอบคำถาม!
Matt Mitchell

4
ปัญหาอื่นของเรื่องนี้คือมันใช้งานได้เฉพาะเมื่อรายการในรายการไม่ซ้ำกัน
CodesInChaos

14

ในที่สุด C # 7 ทำให้เรามีวิธีที่ยอดเยี่ยมในการทำสิ่งนี้:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

ใช่และ MS ควรขยาย CLR / BCL เพื่อทำให้เกิดสิ่งนี้
ทอดด์

14

ทำไมต้องต่างประเทศ!

วิธีที่ง่ายที่สุดคือใช้สำหรับแทน foreach ถ้าคุณกำลังใช้รายการ :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

หรือถ้าคุณต้องการใช้ foreach:

foreach (string m in myList)
{
     // Do something...
}

คุณสามารถใช้สิ่งนี้เพื่อรู้ดัชนีของแต่ละลูป:

myList.indexOf(m)

8
วิธีการแก้ปัญหา indexOf ไม่ถูกต้องสำหรับรายการที่ซ้ำกันและช้ามาก
tymtam

2
ปัญหาที่ต้องหลีกเลี่ยงคือจุดที่คุณสำรวจ IEnumerable หลาย ๆ ครั้งเช่นรับจำนวนไอเท็มจากนั้นนับแต่ละไอเท็ม สิ่งนี้มีความหมายเมื่อ IEnumerable เป็นผลลัพธ์ของการสืบค้นฐานข้อมูล
David Clarke

2
myList.IndexOf () คือ O (n) ดังนั้นลูปของคุณจะเป็น O (n ^ 2)
แพทริคเครา

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

IListนี้จะทำงานสำหรับคอลเลกชันที่สนับสนุน


68
สองปัญหา: 1) แห่งนี้ตั้งO(n^2)ตั้งแต่ในการใช้งานมากที่สุดคือIndexOf O(n)2) สิ่งนี้จะล้มเหลวหากมีรายการที่ซ้ำกันในรายการ
CodesInChaos

15
หมายเหตุ: O (n ^ 2) หมายความว่าการทำเช่นนี้อาจทำให้เกิดความล่าช้าอย่างมากในการรวบรวมชุดใหญ่
O'Rooney

การประดิษฐ์ที่ยอดเยี่ยมในการใช้วิธีการ IndexOf! นี่คือสิ่งที่ฉันกำลังมองหาที่จะได้รับดัชนี (จำนวน) ในวง foreach! Big Thx
Mitja Bonca

19
พระเจ้าฉันหวังว่าคุณไม่ได้ใช้มัน! :( มันใช้ตัวแปรที่คุณไม่ต้องการสร้าง - อันที่จริงแล้วมันจะสร้าง n + 1 ints เพราะฟังก์ชั่นนั้นจะต้องสร้างขึ้นมาหนึ่งตัวเพื่อส่งคืนเช่นกัน - และการค้นหาดัชนีนั้นช้ากว่ามาก การดำเนินการเพิ่มจำนวนเต็มหนึ่งครั้งในทุกขั้นตอนเหตุใดผู้คนจึงไม่โหวตคำตอบนี้
canahari

13
อย่าใช้คำตอบนี้ฉันพบความจริงที่ยากลำบากที่กล่าวถึงในหนึ่งในความคิดเห็น "สิ่งนี้ล้มเหลวหากมีรายการที่ซ้ำกันในรายการ" !!!
บรูซ

9

นี่คือวิธีที่ฉันทำซึ่งดีสำหรับความเรียบง่าย / ความกะทัดรัด แต่ถ้าคุณทำอะไรมากมายในร่างกายลูปobj.Valueมันจะแก่เร็วทีเดียว

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

8

คำตอบนี้: ล็อบบี้ทีมภาษา C # สำหรับการสนับสนุนภาษาโดยตรง

สถานะคำตอบชั้นนำ:

เห็นได้ชัดว่าแนวคิดของดัชนีต่างกับแนวคิดของการแจงนับและไม่สามารถทำได้

แม้ว่าสิ่งนี้จะเป็นจริงของเวอร์ชันภาษา C # ปัจจุบัน (2020) แต่นี่ไม่ใช่ข้อ จำกัด ของแนวคิด CLR / ภาษา แต่ก็สามารถทำได้

ทีมพัฒนาภาษา Microsoft C # สามารถสร้างคุณลักษณะภาษา C # ใหม่ได้โดยเพิ่มการสนับสนุนสำหรับอินเทอร์เฟซ IIndexedEnumerable ใหม่

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

หากforeach ()ใช้และwith var indexมีอยู่แล้วคอมไพเลอร์คาดหวังว่าการรวบรวมรายการจะประกาศIIndexedEnumerableส่วนต่อประสาน หากอินเทอร์เฟซไม่อยู่คอมไพเลอร์สามารถ polyfill ล้อมแหล่งที่มาด้วยวัตถุ IndexedEnumerable ซึ่งเพิ่มในรหัสสำหรับการติดตามดัชนี

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

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

ทำไม:

  • Foreach ดูดีกว่าและในแอปพลิเคชั่นทางธุรกิจลูป foreach นั้นไม่ค่อยมีปัญหาเรื่องคอขวด
  • Foreach สามารถมีประสิทธิภาพมากขึ้นในหน่วยความจำ มีขั้นตอนของฟังก์ชั่นแทนการแปลงเป็นคอลเลกชันใหม่ในแต่ละขั้นตอน ใครจะเป็นผู้ดูแลหากใช้ CPU รอบเพิ่มเติมเมื่อมีข้อผิดพลาดแคช CPU น้อยลงและมีการรวบรวมขยะน้อยลง
  • ต้องการรหัสเพื่อเพิ่มรหัสติดตามดัชนีทำลายความงาม
  • มันค่อนข้างง่ายที่จะติดตั้ง (โปรด Microsoft) และสามารถใช้งานได้แบบย้อนหลัง

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


รอดังนั้นคุณสมบัติภาษานี้มีอยู่แล้วหรือมีการนำเสนอในอนาคตหรือไม่
พาเวล

1
@Pavel ฉันได้อัปเดตคำตอบให้ชัดเจน คำตอบนี้จัดทำขึ้นเพื่อตอบโต้คำตอบนำที่ระบุว่า "เห็นได้ชัดว่าแนวคิดของดัชนีนั้นต่างกับแนวคิดของการแจงนับและไม่สามารถทำได้"
ทอดด์

6

หากคอลเลกชันเป็นรายการคุณสามารถใช้ List.IndexOf ได้เช่นใน:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

13
และตอนนี้อัลกอริทึมคือ O (n ^ 2) (หากไม่ได้แย่ลง) ฉันคิดว่ามากอย่างรอบคอบก่อนที่จะใช้นี้ มันยังเป็นคำตอบที่ซ้ำกันของ @crucible
BradleyDotNET

1
@BradleyDotNET นั้นถูกต้องอย่าใช้รุ่นนี้
Oskar

1
ระวังด้วย! หากคุณมีรายการที่ซ้ำกันในรายการของคุณมันจะได้รับตำแหน่งของรายการแรก!
Sonhja

5

ดีกว่าที่จะใช้การสร้างคำหลักที่continueปลอดภัยเช่นนี้

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

5

คุณสามารถเขียนลูปของคุณเช่นนี้:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

หลังจากเพิ่มโครงสร้างและวิธีการต่อไปนี้

วิธีการ struct และส่วนขยาย encapsulate Enumerable.Select ฟังก์ชันการทำงาน

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

3

วิธีการแก้ปัญหาของฉันสำหรับปัญหานี้คือวิธีขยายWithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

ใช้มันเหมือน

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

ฉันจะใช้structสำหรับคู่ (ดัชนีรายการ)
CodesInChaos

3

เพื่อความสนใจ Phil Haack เพิ่งเขียนตัวอย่างของเรื่องนี้ในบริบทของ Razor Templated Delegate ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

อย่างมีประสิทธิภาพเขาเขียนวิธีการขยายที่ล้อมรอบการวนซ้ำในคลาส "IteratedItem" (ดูด้านล่าง) ช่วยให้การเข้าถึงดัชนีเช่นเดียวกับองค์ประกอบในระหว่างการทำซ้ำ

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

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


3

ฉันไม่คิดว่ามันจะมีประสิทธิภาพมากนัก แต่ใช้งานได้:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

ฉันสร้างสิ่งนี้ในLINQPad :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

คุณสามารถใช้string.join:

var joinResult = string.Join(",", listOfNames);

คุณยังสามารถใช้ string.join ใน c # ตัวอย่างเช่น: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance

1
ใครสามารถอธิบายให้ฉันฟังว่า O (n * n) คืออะไร
Axel

@Axel โดยทั่วไปหมายความว่าการดำเนินการที่จำเป็นในการคำนวณผลลัพธ์จะเพิ่มขึ้นเป็นสองเท่านั่นคือหากมีnรายการการดำเนินการจะเป็นn * nหรือn-squared en.wikipedia.org/wiki/…
Richard Hansell

2

ฉันไม่เชื่อว่ามีวิธีการรับค่าของการวนซ้ำปัจจุบันของ foreach loop การนับตัวเองน่าจะเป็นวิธีที่ดีที่สุด

ฉันขอถามได้ไหมทำไมคุณถึงอยากรู้

ดูเหมือนว่าคุณจะทำสิ่งหนึ่งในสามสิ่งที่ likley:

1) การรับวัตถุจากการรวบรวม แต่ในกรณีนี้คุณมีอยู่แล้ว

2) การนับวัตถุสำหรับการโพสต์ในภายหลัง ... คอลเลกชันมีคุณสมบัตินับที่คุณสามารถใช้ประโยชน์ได้

3) การตั้งค่าคุณสมบัติบนวัตถุตามลำดับในลูป ... แม้ว่าคุณจะสามารถตั้งค่าได้อย่างง่ายดายเมื่อคุณเพิ่มวัตถุในคอลเลกชัน


4) กรณีที่ฉันตีหลายครั้งเป็นสิ่งที่แตกต่างที่ต้องทำในรอบแรกหรือครั้งสุดท้าย - พูดรายการวัตถุที่คุณกำลังจะพิมพ์และคุณต้องใช้เครื่องหมายจุลภาคระหว่างรายการ แต่ไม่ใช่หลังรายการสุดท้าย
Loren Pechtel

2

เว้นแต่ว่าคอลเลกชันของคุณสามารถส่งคืนดัชนีของวัตถุผ่านวิธีการบางอย่างวิธีเดียวคือการใช้เคาน์เตอร์เช่นในตัวอย่างของคุณ

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


2

แล้วเรื่องแบบนี้ล่ะ? โปรดทราบว่า myDelimitedString อาจเป็นโมฆะหาก myEnumerable ว่างเปล่า

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

ปัญหาหลายอย่างที่นี่ A) รั้งพิเศษ B) string concat + = ต่อการวนซ้ำ
enorl76

2

ฉันเพิ่งมีปัญหานี้ แต่การคิดถึงปัญหาในกรณีของฉันให้ทางออกที่ดีที่สุดซึ่งไม่เกี่ยวข้องกับวิธีแก้ปัญหาที่คาดไว้

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

กล่าวคือ

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

ใช้ไม่ได้เสมอ แต่บ่อยครั้งมากพอที่จะพูดถึงฉันคิดว่า

อย่างไรก็ตามประเด็นที่ว่าบางครั้งมีวิธีแก้ปัญหาที่ไม่ชัดเจนในตรรกะที่คุณมี ...


2

ฉันไม่แน่ใจว่าคุณกำลังพยายามทำอะไรกับข้อมูลดัชนีตามคำถาม อย่างไรก็ตามใน C # คุณสามารถปรับวิธี IEnumerable.Select โดยทั่วไปเพื่อดึงดัชนีจากสิ่งที่คุณต้องการ ตัวอย่างเช่นฉันอาจใช้สิ่งนี้เช่นว่าค่าเป็นคี่หรือคู่

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

สิ่งนี้จะให้พจนานุกรมตามชื่อว่ารายการนั้นเป็นเลขคี่ (1) หรือแม้กระทั่ง (0) ในรายการ

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