Bagaimana Anda mendapatkan indeks iterasi loop foreach saat ini?

939

Apakah ada beberapa konstruksi bahasa yang jarang saya temui (seperti beberapa yang saya pelajari baru-baru ini, beberapa di Stack Overflow) di C # untuk mendapatkan nilai yang mewakili iterasi saat ini dari foreach loop?

Misalnya, saya saat ini melakukan sesuatu seperti ini tergantung pada keadaan:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
Matt Mitchell
sumber
1
foreach casting retrieval umumnya tidak akan membuat saya lebih dioptimalkan daripada hanya menggunakan akses berbasis indeks pada koleksi, meskipun dalam banyak kasus itu akan sama. Tujuan dari pendahuluan adalah untuk membuat kode Anda dapat dibaca, tetapi (biasanya) menambahkan lapisan tipuan, yang tidak gratis.
Brian
8
Saya akan mengatakan tujuan utama foreachadalah untuk menyediakan mekanisme iterasi umum untuk semua koleksi terlepas dari apakah mereka dapat diindeks ( List) atau tidak ( Dictionary).
Brian Gideon
2
Hai Brian Gideon - pasti setuju (ini beberapa tahun yang lalu dan saya jauh kurang berpengalaman pada saat itu). Namun, sementara Dictionarytidak dapat diindeks, iterasi Dictionarytidak melewatinya dalam urutan tertentu (yaitu Pencacah dapat diindeks oleh fakta bahwa ia menghasilkan elemen secara berurutan). Dalam pengertian ini, kita dapat mengatakan bahwa kita tidak mencari indeks dalam koleksi, melainkan indeks elemen enumerasi saat ini dalam enumerasi (yaitu apakah kita berada pada elemen enumerasi pertama atau kelima atau terakhir).
Matt Mitchell
4
foreach juga memungkinkan kompiler untuk melewati batas memeriksa setiap akses array dalam kode yang dikompilasi. Menggunakan untuk dengan indeks akan membuat runtime memeriksa apakah akses indeks Anda aman.
IvoTops
1
Tapi itu salah. Jika Anda tidak mengubah variabel iterasi dari loop for dalam loop, kompiler tahu apa batasnya dan tidak perlu memeriksanya lagi. Ini adalah kasus umum bahwa setiap kompiler yang layak akan mengimplementasikannya.
Jim Balter

Jawaban:

552

Ini foreachuntuk pengulangan koleksi yang mengimplementasikan IEnumerable. Ini melakukan ini dengan memanggil GetEnumeratorkoleksi, yang akan mengembalikan sebuahEnumerator .

Enumerator ini memiliki metode dan properti:

  • MoveNext ()
  • Arus

Currentmengembalikan objek tempat Enumerator aktif, MoveNextpembaruanCurrent ke objek berikutnya.

Konsep indeks adalah asing bagi konsep enumerasi, dan tidak dapat dilakukan.

Karena itu, sebagian besar koleksi dapat dilintasi menggunakan pengindeks dan konstruk for loop.

Saya sangat suka menggunakan for for dalam situasi ini dibandingkan dengan melacak indeks dengan variabel lokal.

FlySwat
sumber
165
"Jelas, konsep indeks asing dengan konsep enumerasi, dan tidak dapat dilakukan." - Ini omong kosong, seperti jawaban oleh David B dan bcahill menjelaskan. Indeks adalah enumerasi pada rentang, dan tidak ada alasan seseorang tidak dapat menghitung dua hal secara paralel ... itulah yang dilakukan oleh bentuk pengindeksan Enumerable. Pilih.
Jim Balter
11
Contoh kode dasar:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock
22
@ JimBalter: Itulah tautan pertama yang saya baca. Saya tidak melihat bagaimana itu mendukung posisi Anda. Saya juga tidak melemparkan ad-homs dan kata-kata memecah belah pada orang ketika menjawab. Saya mengutip sebagai contoh penggunaan "omong kosong", "kebingungan luar biasa", "benar-benar salah dan bingung", "Saya mendapat 37 upvotes", dll. (Saya pikir ego Anda sedikit di telepon di sini.) Sebaliknya, saya Saya ingin dengan sopan meminta agar Anda tidak mengecam orang lain dengan 'argumenum ad verecundiam' Anda dan saya ingin mendorong Anda untuk memikirkan cara untuk mendukung orang-orang di StackOverflow di sini. Saya akan mengatakan Jon Skeet adalah warga negara teladan dalam hal ini.
Pretzel
11
Pretzel: Penggunaan Anda Jon Skeet sebagai warga negara model adalah keliru, karena ia akan memukuli (dan menurunkan suara) seseorang yang tidak setuju dengannya, seperti yang saya alami. Jim Balter: Beberapa komentar Anda terlalu agresif dan Anda bisa menunjukkan poin Anda dengan lebih sedikit kemarahan dan lebih banyak pendidikan.
Suncat2000
7
Poin @Pretzel Jim adalah selama Anda dapat memetakan elemen ke urutan bilangan bulat, Anda dapat mengindeksnya. Bahwa kelas itu sendiri tidak menyimpan indeks tidak penting. Selain itu, bahwa daftar tertaut memang memiliki pesanan hanya memperkuat posisi Jim. Yang perlu Anda lakukan adalah memberi nomor setiap elemen secara berurutan. Khususnya, Anda bisa mencapai ini dengan menambah hitungan saat Anda mengulanginya, atau Anda bisa membuat daftar bilangan bulat dengan panjang yang sama dan kemudian meng-zipnya (seperti pada zipfungsi Python ).
jpmc26
666

Ian Mercer memposting solusi serupa dengan ini di blog Phil Haack :

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

Ini memberi Anda item ( item.value) dan indeksnya ( item.i) dengan menggunakan kelebihan LINQ iniSelect :

parameter kedua dari fungsi [di dalam Select] mewakili indeks elemen sumber.

Ini new { i, value }membuat objek anonim baru .

Alokasi tumpukan dapat dihindari dengan menggunakan ValueTuplejika Anda menggunakan C # 7.0 atau lebih baru:

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

Anda juga dapat menghilangkannya item.dengan menggunakan penghancuran otomatis:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>
bcahill
sumber
9
Solusi itu bagus untuk kasus template Razor di mana kerapian template adalah masalah desain non-sepele dan Anda juga ingin menggunakan indeks setiap item yang disebutkan. Namun, perlu diingat bahwa alokasi objek dari 'pembungkus' menambah biaya (dalam ruang dan waktu) di atas penambahan bilangan bulat (yang tidak dapat dihindari).
David Bullock
6
@ mjsr Dokumentasinya ada di sini .
Thorkil Holm-Jacobsen
19
Maaf - itu pintar, tetapi apakah itu benar-benar lebih mudah dibaca daripada membuat indeks di luar foreach dan menambahkannya setiap loop?
jbyrd
13
Dengan versi C # selanjutnya sehingga Anda juga menggunakan tupel, sehingga Anda akan memiliki sesuatu seperti ini: foreach (var (item, i) di Model.Select ((v, i) => (v, i))) Memungkinkan Anda untuk mengakses item dan indeks (i) langsung di dalam for-loop dengan dekonstruksi tuple.
Haukman
5
Dapatkah seseorang menjelaskan kepada saya mengapa ini adalah jawaban yang bagus (lebih dari 450 suara positif pada saat penulisan)? Sejauh yang saya bisa lihat, ini lebih sulit untuk dipahami daripada hanya menambah penghitung, karena itu kurang dapat dirawat, ia menggunakan lebih banyak memori, dan mungkin lebih lambat. Apakah saya melewatkan sesuatu?
Rich N
182

Akhirnya C # 7 memiliki sintaks yang layak untuk mendapatkan indeks di dalam foreachloop (yaitu tuple):

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

Diperlukan sedikit metode penyuluhan:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 
pengguna1414213562
sumber
8
Jawaban ini diremehkan, memiliki tuple jauh lebih bersih
Todd
7
Dimodifikasi untuk menangani koleksi nol:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Muat
Bagus Saya sangat suka solusi ini.
FranzHuber23
Ini adalah jawaban terbaik
w0ns88
2
Mungkin berguna untuk memanggil metode Enumeratedagar lebih dikenali bagi orang-orang yang terbiasa dengan bahasa lain (dan mungkin juga menukar urutan parameter tuple). Lagipula WithIndexbukan itu tidak jelas.
FernAndr
115

Bisa melakukan sesuatu seperti ini:

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));
    }
}
Brad Wilson
sumber
12
Itu "tidak benar-benar" menyelesaikan masalah. Idenya bagus tetapi tidak menghindari variabel penghitungan tambahan
Atmocreations
Ini tidak berfungsi jika kami memiliki pernyataan pengembalian dalam loop for kami, jika Anda mengubah "ForEachWithIndex" untuk itu, maka itu tidak generik, lebih baik menulis reguler untuk loop
Shankar Raju
Panggilan ForEachWithIndex Anda setara dengan yang ini menggunakan Linq Select yang mengambil string dan indeks:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861
95

Saya tidak setuju dengan komentar bahwa forloop adalah pilihan yang lebih baik dalam banyak kasus.

foreach adalah konstruk yang bermanfaat, dan bukan replaceble oleh a for loop dalam semua keadaan.

Misalnya, jika Anda memiliki DataReader dan loop melalui semua catatan menggunakan foreachitu secara otomatis memanggil Buang metode dan menutup pembaca (yang kemudian dapat menutup koneksi secara otomatis). Karenanya ini lebih aman karena mencegah kebocoran koneksi walaupun Anda lupa menutup pembaca.

(Tentu itu adalah praktik yang baik untuk selalu menutup pembaca tetapi kompiler tidak akan menangkapnya jika Anda tidak - Anda tidak dapat menjamin Anda telah menutup semua pembaca tetapi Anda dapat membuatnya lebih mungkin Anda tidak akan membocorkan koneksi dengan mendapatkan dalam kebiasaan menggunakan foreach.)

Mungkin ada contoh lain dari panggilan implisit dari Disposemetode yang bermanfaat.

mike nelson
sumber
2
Terima kasih telah menunjukkan ini. Agak halus. Anda dapat memperoleh informasi lebih lanjut di pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator dan msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer
+1. Saya menulis lebih detail tentang foreachperbedaan for(dan lebih dekat dengan while) di Programmers.SE .
Arseni Mourzenko
64

Jawaban Literal - peringatan, kinerja mungkin tidak sebagus hanya menggunakan a intuntuk melacak indeks. Setidaknya lebih baik daripada menggunakan IndexOf.

Anda hanya perlu menggunakan indeks berlebihan untuk memilih untuk membungkus setiap item dalam koleksi dengan objek anonim yang mengetahui indeks. Ini dapat dilakukan terhadap apa pun yang mengimplementasikan 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);
}
Amy B
sumber
3
Satu-satunya alasan untuk menggunakan OfType <T> () alih-alih Cast <T> () adalah jika beberapa item dalam enumerasi mungkin gagal melakukan cast secara eksplisit. Untuk objek, ini tidak akan pernah terjadi.
dahlbyk
13
Tentu, kecuali alasan lain untuk menggunakan OfType alih-alih Cast - yang mana saya tidak pernah menggunakan Cast.
Amy B
36

Menggunakan LINQ, C # 7, dan System.ValueTuplepaket NuGet, Anda bisa melakukan ini:

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

Anda dapat menggunakan foreachkonstruk reguler dan dapat mengakses nilai dan indeks secara langsung, bukan sebagai anggota objek, dan membuat kedua bidang hanya dalam lingkup loop. Untuk alasan ini, saya percaya ini adalah solusi terbaik jika Anda dapat menggunakan C # 7 dan System.ValueTuple.

Pavel
sumber
Bagaimana ini berbeda dari jawaban user1414213562 ?
Edward Brey
@ EdwardBrey kurasa tidak. Pertanyaan ini memiliki banyak jawaban, saya mungkin hanya melewatkannya atau tidak memperhatikan apa yang dilakukannya karena dia memisahkan beberapa logika menjadi metode ekstensi.
Pavel
1
Ini berbeda karena .Pilih bawaan dari LINQ. Anda tidak harus menulis fungsi sendiri? Anda perlu memiliki VS menginstal "System.ValueTuple" sekalipun.
Anton
33

Menggunakan jawaban @ FlySwat, saya datang dengan solusi ini:

//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
}

Anda mendapatkan enumerator menggunakan GetEnumeratordan kemudian Anda menggunakan forloop. Namun, triknya adalah membuat kondisi loop listEnumerator.MoveNext() == true.

Karena MoveNextmetode enumerator mengembalikan true jika ada elemen berikutnya dan itu dapat diakses, membuat kondisi loop membuat loop berhenti ketika kita kehabisan elemen untuk beralih.

Gezim
sumber
10
Tidak perlu membandingkan listEnumerator.MoveNext () == true. Itu seperti bertanya pada komputer apakah benar == benar? :) Katakan saja jika listEnumerator.MoveNext () {}
Zesty
9
@Zesty, Anda benar sekali. Saya merasa lebih mudah untuk menambahkannya dalam kasus ini terutama bagi orang-orang yang tidak terbiasa memasukkan apa pun selain saya <blahSize sebagai syarat.
Gezim
@ Gezim Saya tidak keberatan predikat di sini, tapi saya mengerti maksud Anda bahwa ini tidak terlihat seperti predikat.
John Dvorak
1
Anda harus membuang enumerator.
Antonín Lejsek
@ AntonínLejsek Pencacah tidak menerapkan IDisposable.
Edward Brey
27

Tidak ada yang salah dengan menggunakan variabel penghitung. Bahkan, apakah Anda menggunakan for, foreach whileatau do, variabel counter harus dideklarasikan dan ditingkatkan di suatu tempat.

Jadi gunakan idiom ini jika Anda tidak yakin apakah Anda memiliki koleksi yang diindeks sesuai:

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

Lain gunakan yang ini jika Anda tahu bahwa koleksi Anda yang dapat diindeks adalah O (1) untuk akses indeks (yang akan untuk Arraydan mungkin untuk List<T>(dokumentasi tidak mengatakan), tetapi tidak harus untuk jenis lain (seperti 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'
}

Seharusnya tidak perlu untuk 'secara manual' mengoperasikannya IEnumeratordengan memohon MoveNext()dan menginterogasi Current- foreachmenyelamatkan Anda dari gangguan khusus itu ... jika Anda perlu melewatkan item, cukup gunakan a continuedi badan loop.

Dan hanya untuk kelengkapan, tergantung pada apa yang Anda lakukan dengan indeks Anda (konstruksi di atas menawarkan banyak fleksibilitas), Anda dapat menggunakan LINQ Paralel:

// 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));

Kami menggunakan di AsParallel()atas, karena ini sudah 2014, dan kami ingin memanfaatkan banyak inti untuk mempercepat. Lebih lanjut, untuk LINQ 'berurutan', Anda hanya mendapatkan ForEach()metode ekstensi List<T>danArray ... dan tidak jelas bahwa menggunakannya lebih baik daripada melakukan yang sederhana foreach, karena Anda masih menjalankan single-threaded untuk sintaks yang lebih buruk.

David Bullock
sumber
26

Anda dapat membungkus enumerator asli dengan enumerator lain yang berisi informasi indeks.

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

Ini adalah kode untuk ForEachHelperkelas.

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;
        }            
    }
}
Brian Gideon
sumber
Ini sebenarnya tidak akan mengembalikan indeks item. Sebagai gantinya, itu akan mengembalikan indeks di dalam daftar yang disebutkan, yang mungkin hanya sublist dari daftar, dengan demikian memberi Anda data yang akurat hanya ketika sublist dan daftar memiliki ukuran yang sama. Pada dasarnya, setiap kali koleksi memiliki objek di dalamnya tidak dalam tipe yang diminta indeks Anda akan salah.
Lucas B
7
@Lucas: Tidak, tapi itu akan mengembalikan indeks iterasi setiap pendakian saat ini. Itu pertanyaannya.
Brian Gideon
20

Inilah solusi yang baru saja saya buat untuk masalah ini

Kode asli:

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

Kode yang diperbarui

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

Metode ekstensi:

    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;
    }
mat3
sumber
16

Cukup tambahkan indeks Anda sendiri. Tetap sederhana.

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

Ini hanya akan berfungsi untuk Daftar dan bukan IEnumerable, tetapi di LINQ ada ini:

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 Saya tidak mengatakan itu adalah jawaban yang bagus, saya hanya mengatakan itu hanya menunjukkan itu mungkin untuk melakukan apa yang dia minta :)

@Graphain Saya tidak berharap ini akan cepat - Saya tidak sepenuhnya yakin cara kerjanya, itu bisa mengulangi seluruh daftar setiap kali untuk menemukan objek yang cocok, yang akan menjadi semacam perbandingan.

Yang mengatakan, Daftar mungkin menyimpan indeks dari setiap objek bersama dengan hitungan.

Jonathan tampaknya punya ide yang lebih baik, apakah dia mau menjelaskan?

Akan lebih baik untuk tetap menghitung sampai di mana Anda tahu, lebih sederhana, dan lebih mudah beradaptasi.

percobaan
sumber
5
Tidak yakin tentang downvoting berat. Tentu kinerja membuat ini menjadi penghalang tetapi Anda menjawab pertanyaan!
Matt Mitchell
4
Masalah lain dengan ini adalah bahwa itu hanya berfungsi jika item dalam daftar adalah unik.
CodesInChaos
14

C # 7 akhirnya memberi kita cara yang elegan untuk melakukan ini:

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}");
        }
    }
}
Paul Mitchell
sumber
Ya, dan MS harus memperpanjang CLR / BCL untuk menjadikan hal ini asli.
Todd
14

Kenapa harus tahu dulu ?!

Cara termudah adalah menggunakan untuk bukannya foreach jika Anda menggunakan Daftar :

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

Atau jika Anda ingin menggunakan foreach:

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

Anda dapat menggunakan ini untuk mengetahui indeks setiap loop:

myList.indexOf(m)
Parsa
sumber
8
Solusi indexOf tidak valid untuk daftar dengan duplikat dan juga sangat lambat.
tymtam
2
Masalah yang harus dihindari adalah di mana Anda melewati IEnumerable beberapa kali, misalnya untuk mendapatkan jumlah item dan kemudian setiap item. Ini memiliki implikasi ketika IEnumerable adalah hasil dari query database misalnya.
David Clarke
2
myList.IndexOf () adalah O (n), jadi loop Anda akan menjadi O (n ^ 2).
Patrick Beard
9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Ini akan berfungsi untuk mendukung koleksi IList.

Sachin
sumber
68
Dua masalah: 1) Ini O(n^2)karena dalam kebanyakan implementasi IndexOfadalah O(n). 2) Ini gagal jika ada item duplikat dalam daftar.
CodesInChaos
15
Catatan: O (n ^ 2) berarti ini bisa sangat lambat untuk koleksi besar.
O'Rooney
Penemuan hebat untuk menggunakan metode IndexOf! Inilah yang saya cari untuk mendapatkan indeks (angka) di foreach loop! Big Thx
Mitja Bonca
19
Ya Tuhan, saya harap Anda tidak menggunakannya! :( Ini TIDAK menggunakan variabel yang tidak ingin Anda buat - pada kenyataannya, itu akan membuat n +1 int karena fungsi itu harus membuat satu untuk kembali juga, - dan bahwa indeks pencarian jauh, jauh lebih lambat daripada satu operasi kenaikan bilangan bulat di setiap langkah. Mengapa orang tidak akan memilih jawaban ini?
canahari
13
Jangan gunakan jawaban ini, saya menemukan kebenaran keras yang disebutkan dalam salah satu komentar. "Ini gagal jika ada item duplikat dalam daftar." !!!
Bruce
9

Ini adalah bagaimana saya melakukannya, yang bagus untuk kesederhanaan / singkatnya, tetapi jika Anda melakukan banyak hal dalam lingkaran obj.Value, itu akan menjadi tua dengan cepat.

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

Jawaban ini: melobi tim bahasa C # untuk dukungan bahasa langsung.

Jawaban terkemuka menyatakan:

Jelas, konsep indeks asing dengan konsep enumerasi, dan tidak dapat dilakukan.

Meskipun ini benar untuk versi bahasa C # saat ini (2020), ini bukan batas CLR / Bahasa konseptual, ini bisa dilakukan.

Tim pengembangan bahasa C # Microsoft dapat membuat fitur bahasa C # baru, dengan menambahkan dukungan untuk Interface baru 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);
}

Jika foreach ()digunakan dan with var indexada, maka kompiler mengharapkan koleksi item untuk menyatakan IIndexedEnumerableantarmuka. Jika antarmuka tidak ada, kompiler dapat polyfill membungkus sumber dengan objek IndexedEnumerable, yang menambahkan kode untuk melacak indeks.

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

Nantinya, CLR dapat diperbarui untuk memiliki pelacakan indeks internal, yang hanya digunakan jika withkata kunci ditentukan dan sumber tidak langsung menerapkanIIndexedEnumerable

Mengapa:

  • Foreach terlihat lebih bagus, dan dalam aplikasi bisnis, foreach loop jarang menjadi hambatan kinerja
  • Foreach dapat lebih efisien dalam hal memori. Memiliki pipeline fungsi alih-alih mengonversi ke koleksi baru di setiap langkah. Siapa yang peduli jika menggunakan lebih banyak siklus CPU ketika ada lebih sedikit kesalahan cache CPU dan lebih sedikit pengumpulan sampah?
  • Mewajibkan pembuat kode untuk menambahkan kode pelacakan indeks, merusak keindahan
  • Ini cukup mudah untuk diterapkan (harap Microsoft) dan kompatibel dengan versi sebelumnya

Sementara kebanyakan orang di sini tidak karyawan Microsoft, ini adalah sebuah jawaban yang benar, Anda dapat melobi Microsoft untuk menambahkan fitur tersebut. Anda sudah bisa membangun iterator Anda sendiri dengan fungsi ekstensi dan menggunakan tupel , tetapi Microsoft dapat menaburkan gula sintaksis untuk menghindari fungsi ekstensi

Todd
sumber
Tunggu, jadi apakah fitur bahasa ini sudah ada, atau diusulkan untuk masa depan?
Pavel
1
@Pavel Saya memperbarui jawabannya menjadi jelas. Jawaban ini diberikan untuk melawan jawaban utama yang menyatakan "Jelas, konsep indeks asing dengan konsep enumerasi, dan tidak dapat dilakukan."
Todd
6

Jika koleksi adalah daftar, Anda dapat menggunakan List.IndexOf, seperti pada:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
ssaeed
sumber
13
Dan sekarang algoritmanya adalah O (n ^ 2) (jika tidak lebih buruk). Saya akan berpikir sangat hati-hati sebelum menggunakan ini. Ini juga merupakan duplikat dari jawaban
@crucible
1
@BradleyDotNET benar, jangan gunakan versi ini.
Oskar
1
Hati-hati dengan ini! Jika Anda memiliki item duplikat dalam daftar Anda, itu akan mendapatkan posisi yang pertama!
Sonhja
5

Lebih baik menggunakan continuekonstruksi kata kunci yang aman seperti ini

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

Anda dapat menulis loop seperti ini:

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

Setelah menambahkan struct dan metode ekstensi berikut.

Metode struct dan ekstensi merangkum fungsionalitas 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);
    }
}
Puas
sumber
3

Solusi saya untuk masalah ini adalah metode ekstensi WithIndex(),

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

Gunakan seperti

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));
ulrichb
sumber
Saya akan menggunakan pasangan struct(indeks, item).
CodesInChaos
3

Yang menarik, Phil Haack baru saja menulis sebuah contoh tentang hal ini dalam konteks Delegasi Templated Razor ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Secara efektif ia menulis metode ekstensi yang membungkus iterasi dalam kelas "IteratedItem" (lihat di bawah) yang memungkinkan akses ke indeks serta elemen selama iterasi.

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; }
}

Namun, sementara ini akan baik-baik saja di lingkungan non-Razor jika Anda melakukan operasi tunggal (yaitu operasi yang dapat disediakan sebagai lambda) itu tidak akan menjadi pengganti yang solid untuk sintaks for / foreach dalam konteks non-Razor .

Matt Mitchell
sumber
3

Saya tidak berpikir ini seharusnya cukup efisien, tetapi berfungsi:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
Bart Calixto
sumber
3

Saya membangun ini di 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.

Anda juga bisa menggunakan string.join:

var joinResult = string.Join(",", listOfNames);
Warren LaFrance
sumber
Anda juga dapat menggunakan string.join di c # Sebagai contoh: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance
1
Bisakah seseorang menjelaskan kepada saya apa hal O (n * n) ini?
Axel
@Axel pada dasarnya berarti bahwa operasi yang diperlukan untuk menghitung hasil meningkat secara kuadrat, yaitu jika ada nitem maka operasi n * n, atau n-squared. en.wikipedia.org/wiki/…
Richard Hansell
2

Saya tidak percaya ada cara untuk mendapatkan nilai dari iterasi loop foreach saat ini. Menghitung diri sendiri, tampaknya menjadi cara terbaik.

Bolehkah saya bertanya, mengapa Anda ingin tahu?

Tampaknya Anda paling mungkin melakukan satu dari tiga hal:

1) Mendapatkan objek dari koleksi, tetapi dalam hal ini Anda sudah memilikinya.

2) Menghitung objek untuk pemrosesan pos nanti ... koleksi memiliki properti Count yang dapat Anda manfaatkan.

3) Mengatur properti pada objek berdasarkan urutannya dalam loop ... meskipun Anda dapat dengan mudah mengaturnya saat Anda menambahkan objek ke koleksi.

bryansh
sumber
4) Kasing yang saya tekan beberapa kali adalah sesuatu yang berbeda yang harus dilakukan pada lintasan pertama atau terakhir - katakan daftar objek yang akan Anda cetak dan Anda perlu koma di antara item tetapi tidak setelah item terakhir.
Loren Pechtel
2

Kecuali koleksi Anda dapat mengembalikan indeks objek melalui beberapa metode, satu-satunya cara adalah menggunakan penghitung seperti pada contoh Anda.

Namun, ketika bekerja dengan indeks, satu-satunya jawaban yang masuk akal untuk masalah ini adalah menggunakan for for. Ada lagi yang memperkenalkan kompleksitas kode, belum lagi kompleksitas waktu dan ruang.

Joseph Daigle
sumber
2

Bagaimana dengan sesuatu yang seperti ini? Perhatikan bahwa myDelimitedString mungkin nol jika myEnumerable kosong.

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;
}
Menara Matt
sumber
Berbagai masalah di sini. A) penjepit ekstra. B) string concat + = per iterasi loop.
enorl76
2

Saya baru saja mengalami masalah ini, tetapi memikirkan masalah dalam kasus saya memberikan solusi terbaik, tidak terkait dengan solusi yang diharapkan.

Ini bisa menjadi kasus yang cukup umum, pada dasarnya, saya membaca dari satu daftar sumber dan membuat objek berdasarkan pada mereka dalam daftar tujuan, namun, saya harus memeriksa apakah item sumber valid terlebih dahulu dan ingin mengembalikan baris dari sembarang kesalahan. Pada pandangan pertama, saya ingin memasukkan indeks ke enumerator objek di properti saat ini, namun, ketika saya menyalin elemen-elemen ini, saya secara implisit mengetahui indeks saat ini dari tujuan saat ini. Jelas itu tergantung pada objek tujuan Anda, tetapi bagi saya itu adalah Daftar, dan kemungkinan besar itu akan mengimplementasikan ICollection.

yaitu

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]});
  }
}

Tidak selalu berlaku, tetapi sering cukup layak disebut, saya pikir.

Pokoknya, intinya adalah bahwa kadang-kadang ada solusi yang tidak jelas dalam logika yang Anda miliki ...

nicodemus13
sumber
2

Saya tidak yakin apa yang Anda coba lakukan dengan informasi indeks berdasarkan pertanyaan. Namun, dalam C #, Anda biasanya dapat mengadaptasi metode IEnumerable.Select untuk mendapatkan indeks dari apa pun yang Anda inginkan. Misalnya, saya mungkin menggunakan sesuatu seperti ini untuk mengetahui apakah nilainya ganjil atau genap.

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);

Ini akan memberi Anda kamus dengan nama apakah item itu aneh (1) atau genap (0) dalam daftar.

Kasey Speakman
sumber