C # foreach improvement?

9

Saya sering mengalami hal ini selama pemrograman di mana saya ingin memiliki loop count index di dalam foreach dan harus membuat integer, menggunakannya, increment, dll. Bukankah lebih baik jika ada kata kunci yang diperkenalkan yang jumlah loop di dalam foreach? Itu juga bisa digunakan dalam loop lain juga.

Apakah ini bertentangan dengan penggunaan kata kunci yang melihat bagaimana kompiler tidak akan mengizinkan kata kunci ini digunakan di mana pun kecuali dalam pembuatan loop?

Justin
sumber
7
Anda dapat melakukannya sendiri dengan metode ekstensi, lihat jawaban dari Dan Finch: stackoverflow.com/a/521894/866172 (bukan jawaban dengan nilai terbaik, tapi saya pikir itu yang "bersih")
Jalayn
Perl dan hal-hal seperti $ _ terlintas dalam pikiran. Aku benci itu.
Yam Marcovic
2
Dari apa yang saya ketahui tentang C #, ia memiliki banyak kata kunci berbasis konteks, jadi ini tidak akan "bertentangan dengan penggunaan kata kunci". Seperti yang ditunjukkan dalam jawaban di bawah ini, Anda mungkin hanya ingin menggunakan for () loop.
Craige
@Jalayn Silakan posting itu sebagai jawaban. Ini adalah salah satu pendekatan terbaik.
Apoorv Khurasia
@ MonsterTruck: Saya rasa dia tidak seharusnya. Solusi sebenarnya adalah dengan memigrasikan pertanyaan ini ke SO dan menutupnya sebagai duplikat dari tautan yang ditanyakan Jalayn.
Brian

Jawaban:

54

Jika Anda membutuhkan jumlah loop di dalam foreachloop, mengapa Anda tidak menggunakan forloop biasa . The foreachLoop dimaksudkan untuk membuat penggunaan tertentu dari forloop sederhana. Sepertinya Anda memiliki situasi di mana kesederhanaan foreachtidak lagi bermanfaat.

Ryathal
sumber
1
+1 - poin bagus. Cukup mudah menggunakan IEnumerable.Count atau object.length dengan regular for loop untuk menghindari masalah dengan mencari tahu jumlah elemen dalam objek.
11
@ GlenH7: Bergantung pada implementasinya, IEnumerable.Countbisa jadi mahal (atau tidak mungkin, jika itu hanya penghitungan satu kali). Anda perlu tahu apa sumber yang mendasarinya sebelum Anda bisa melakukannya dengan aman.
Binary Worrier
2
-1: Bill Wagner di Lebih Efektif C # menjelaskan mengapa orang harus selalu menempel foreachlebih for (int i = 0…). Dia telah menulis beberapa halaman tetapi saya dapat meringkas dalam dua kata - optimasi kompiler pada iterasi. Oke, itu bukan dua kata.
Apoorv Khurasia
13
@ MonsterTruck: apa pun yang ditulis Bill Wagner, saya telah melihat cukup banyak situasi seperti yang dijelaskan oleh OP, di mana sebuah forloop memberikan kode yang lebih mudah dibaca (dan optimisasi kompiler teoretis sering merupakan optimasi prematur).
Doc Brown
1
@DocBrown Ini tidak sesederhana optimasi prematur. Saya sendiri telah mengidentifikasi kemacetan di masa lalu dan memperbaikinya menggunakan pendekatan ini (tapi saya akui saya berurusan dengan ribuan objek dalam koleksi). Berharap untuk segera mengirimkan contoh yang berfungsi. Sementara itu, di sini adalah Jon Skeet . [Dia mengatakan peningkatan kinerja tidak akan signifikan tetapi itu tergantung banyak pada koleksi dan jumlah objek].
Apoorv Khurasia
37

Anda dapat menggunakan jenis anonim seperti ini

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

Ini mirip dengan enumeratefungsi Python

for i, v in enumerate(items):
    print v, i
Kris Harper
sumber
+1 Oh ... bola lampu. Saya suka pendekatan itu. Mengapa saya tidak pernah memikirkannya sebelumnya?
Phil
17
Siapa pun yang lebih suka ini dalam C # daripada klasik untuk loop jelas memiliki pendapat aneh tentang keterbacaan. Ini mungkin trik yang rapi tetapi saya tidak akan membiarkan hal seperti itu dalam kode kualitas produksi. Ini jauh lebih berisik daripada klasik untuk loop dan tidak dapat dipahami dengan cepat.
Falcon
4
@ Falcon, ini adalah alternatif yang valid JIKA ANDA TIDAK BISA MENGGUNAKAN mode lama untuk loop (yang membutuhkan hitungan). Itu adalah beberapa koleksi objek yang harus saya kerjakan ...
Fabricio Araujo
8
@FabricioAraujo: Tentunya C # untuk loop tidak memerlukan hitungan. Sejauh yang saya tahu mereka sama dengan C untuk loop, yang berarti Anda dapat menggunakan nilai boolean untuk mengakhiri loop. Seperti cek untuk akhir enumerasi ketika MoveNext mengembalikan false.
Zan Lynx
1
Ini memiliki manfaat tambahan karena memiliki semua kode untuk menangani kenaikan pada awal blok foreach daripada harus menemukannya.
JeffO
13

Saya hanya menambahkan jawaban Dan Finch di sini, seperti yang diminta.

Jangan beri aku poin, beri poin ke Dan Finch :-)

Solusinya adalah menulis metode ekstensi generik berikut:

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

Yang digunakan seperti ini:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
sumber
2
Itu cukup pintar, tapi saya pikir hanya menggunakan normal untuk lebih "mudah dibaca", terutama jika seseorang yang akhirnya mempertahankan kode mungkin tidak banyak as di NET. Dan tidak terlalu akrab dengan konsep metode ekstensi . Saya masih mengambil sedikit kode ini. :)
MetalMikester
1
Bisa memperdebatkan kedua sisi ini, dan pertanyaan awal - Saya setuju dengan maksud poster, ada kasus di mana foreach membaca lebih baik tetapi indeks diperlukan, namun saya selalu tidak suka menambahkan lebih banyak gula sintaksis ke bahasa jika tidak sangat dibutuhkan - Saya memiliki solusi yang persis sama dalam kode saya sendiri dengan nama yang berbeda - strings.ApplyIndexed <T> (......)
Mark Mullin
Saya setuju dengan Mark Mullin, Anda bisa berdebat dengan kedua belah pihak. Saya pikir itu adalah penggunaan metode ekstensi yang cukup pintar dan cocok dengan pertanyaan jadi saya membaginya.
Jalayn
5

Saya mungkin salah tentang hal ini, tetapi saya selalu berpikir bahwa titik utama dari foreach loop adalah untuk menyederhanakan iterasi dari hari STL dari C ++ yaitu

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

bandingkan ini dengan .NET penggunaan IEnumerable di muka

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

yang terakhir jauh lebih sederhana dan menghindari banyak perangkap lama. Selain itu, mereka tampaknya mengikuti aturan yang hampir identik. Misalnya Anda tidak dapat mengubah konten IEnumerable saat berada di dalam loop (yang jauh lebih masuk akal jika Anda menganggapnya sebagai cara STL). Namun, for loop seringkali memiliki tujuan logis yang berbeda dari iterasi.

Jonathan Henson
sumber
4
+1: beberapa orang ingat bahwa foreach pada dasarnya adalah gula sintaksis atas pola iterator.
Steven Evers
1
Ya ada beberapa perbedaan; manipulasi pointer di mana sekarang. NET disembunyikan cukup dalam dari programmer, tetapi Anda bisa menulis untuk loop yang sangat mirip dengan contoh C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS
@KeithS, tidak jika Anda menyadari bahwa semua yang Anda sentuh. NET yang bukan tipe terstruktur ADALAH POINTER, hanya dengan sintaks yang berbeda (. Bukannya ->) Pada kenyataannya, meneruskan tipe referensi ke suatu metode sama dengan melewatkannya sebagai sebuah pointer dan melewatkannya dengan referensi adalah meneruskannya sebagai sebuah pointer ganda. Hal yang sama. Setidaknya, begitulah cara orang yang menggunakan bahasa asli C ++ memikirkannya. Seperti, Anda belajar bahasa Spanyol di sekolah dengan memikirkan tentang bagaimana bahasa tersebut secara sintaksis terkait dengan bahasa ibu Anda.
Jonathan Henson
* tipe nilai bukan tipe terstruktur. Maaf.
Jonathan Henson
2

Jika koleksi yang dimaksud adalah IList<T>, Anda cukup menggunakan fordengan pengindeksan:

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

Jika tidak, maka Anda bisa menggunakannya Select(), itu memiliki kelebihan yang memberi Anda indeks.

Jika itu juga tidak cocok, saya pikir mempertahankan indeks secara manual cukup sederhana, itu hanya dua baris kode yang sangat pendek. Membuat kata kunci khusus untuk ini akan menjadi kerja keras.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
sumber
+1, juga, jika Anda menggunakan indeks tepat sekali di dalam loop, Anda bisa melakukan sesuatu seperti foo(++i)atau foo(i++)tergantung pada indeks mana yang diperlukan.
Pekerjaan
2

Jika yang Anda tahu adalah bahwa koleksi tersebut adalah jumlah IEn, tetapi perlu melacak berapa banyak elemen yang telah Anda proses sejauh ini (dan dengan demikian total ketika Anda selesai), Anda dapat menambahkan beberapa baris ke dasar untuk loop:

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
}

Anda juga bisa menambahkan variabel indeks yang ditambahkan ke foreach:

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

Jika Anda ingin membuat ini terlihat lebih elegan, Anda dapat menentukan satu atau dua metode ekstensi untuk "menyembunyikan" detail ini:

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()));
KeithS
sumber
1

Pelingkupan juga berfungsi jika Anda ingin menjaga blok bersih dari tabrakan dengan nama lain ...

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

Untuk ini saya menggunakan whileloop. Loop dengan forjuga bekerja, dan banyak orang tampaknya lebih suka mereka, tetapi saya hanya suka menggunakan fordengan sesuatu yang akan memiliki jumlah iterasi yang tetap. IEnumerables dapat dihasilkan pada saat run time, jadi, menurut saya, whilelebih masuk akal. Sebut saja panduan pengodean informal, jika Anda mau.

Robotman
sumber