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++;
}
foreach
adalah untuk menyediakan mekanisme iterasi umum untuk semua koleksi terlepas dari apakah mereka dapat diindeks (List
) atau tidak (Dictionary
).Dictionary
tidak dapat diindeks, iterasiDictionary
tidak 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).Jawaban:
Ini
foreach
untuk pengulangan koleksi yang mengimplementasikanIEnumerable
. Ini melakukan ini dengan memanggilGetEnumerator
koleksi, yang akan mengembalikan sebuahEnumerator
.Enumerator ini memiliki metode dan properti:
Current
mengembalikan objek tempat Enumerator aktif,MoveNext
pembaruanCurrent
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.
sumber
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
fungsi Python ).Ian Mercer memposting solusi serupa dengan ini di blog Phil Haack :
Ini memberi Anda item (
item.value
) dan indeksnya (item.i
) dengan menggunakan kelebihan LINQ iniSelect
:Ini
new { i, value }
membuat objek anonim baru .Alokasi tumpukan dapat dihindari dengan menggunakan
ValueTuple
jika Anda menggunakan C # 7.0 atau lebih baru:Anda juga dapat menghilangkannya
item.
dengan menggunakan penghancuran otomatis:sumber
Akhirnya C # 7 memiliki sintaks yang layak untuk mendapatkan indeks di dalam
foreach
loop (yaitu tuple):Diperlukan sedikit metode penyuluhan:
sumber
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
agar lebih dikenali bagi orang-orang yang terbiasa dengan bahasa lain (dan mungkin juga menukar urutan parameter tuple). LagipulaWithIndex
bukan itu tidak jelas.Bisa melakukan sesuatu seperti ini:
sumber
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Saya tidak setuju dengan komentar bahwa
for
loop adalah pilihan yang lebih baik dalam banyak kasus.foreach
adalah konstruk yang bermanfaat, dan bukan replaceble oleh afor
loop dalam semua keadaan.Misalnya, jika Anda memiliki DataReader dan loop melalui semua catatan menggunakan
foreach
itu 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
Dispose
metode yang bermanfaat.sumber
foreach
perbedaanfor
(dan lebih dekat denganwhile
) di Programmers.SE .Jawaban Literal - peringatan, kinerja mungkin tidak sebagus hanya menggunakan a
int
untuk melacak indeks. Setidaknya lebih baik daripada menggunakanIndexOf
.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.
sumber
Menggunakan LINQ, C # 7, dan
System.ValueTuple
paket NuGet, Anda bisa melakukan ini:Anda dapat menggunakan
foreach
konstruk 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 danSystem.ValueTuple
.sumber
Menggunakan jawaban @ FlySwat, saya datang dengan solusi ini:
Anda mendapatkan enumerator menggunakan
GetEnumerator
dan kemudian Anda menggunakanfor
loop. Namun, triknya adalah membuat kondisi looplistEnumerator.MoveNext() == true
.Karena
MoveNext
metode enumerator mengembalikan true jika ada elemen berikutnya dan itu dapat diakses, membuat kondisi loop membuat loop berhenti ketika kita kehabisan elemen untuk beralih.sumber
IDisposable
.Tidak ada yang salah dengan menggunakan variabel penghitung. Bahkan, apakah Anda menggunakan
for
,foreach
while
ataudo
, variabel counter harus dideklarasikan dan ditingkatkan di suatu tempat.Jadi gunakan idiom ini jika Anda tidak yakin apakah Anda memiliki koleksi yang diindeks sesuai:
Lain gunakan yang ini jika Anda tahu bahwa koleksi Anda yang dapat diindeks adalah O (1) untuk akses indeks (yang akan untuk
Array
dan mungkin untukList<T>
(dokumentasi tidak mengatakan), tetapi tidak harus untuk jenis lain (sepertiLinkedList
)):Seharusnya tidak perlu untuk 'secara manual' mengoperasikannya
IEnumerator
dengan memohonMoveNext()
dan menginterogasiCurrent
-foreach
menyelamatkan Anda dari gangguan khusus itu ... jika Anda perlu melewatkan item, cukup gunakan acontinue
di 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:
Kami menggunakan di
AsParallel()
atas, karena ini sudah 2014, dan kami ingin memanfaatkan banyak inti untuk mempercepat. Lebih lanjut, untuk LINQ 'berurutan', Anda hanya mendapatkanForEach()
metode ekstensiList<T>
danArray
... dan tidak jelas bahwa menggunakannya lebih baik daripada melakukan yang sederhanaforeach
, karena Anda masih menjalankan single-threaded untuk sintaks yang lebih buruk.sumber
Anda dapat membungkus enumerator asli dengan enumerator lain yang berisi informasi indeks.
Ini adalah kode untuk
ForEachHelper
kelas.sumber
Inilah solusi yang baru saja saya buat untuk masalah ini
Kode asli:
Kode yang diperbarui
Metode ekstensi:
sumber
Cukup tambahkan indeks Anda sendiri. Tetap sederhana.
sumber
Ini hanya akan berfungsi untuk Daftar dan bukan IEnumerable, tetapi di LINQ ada ini:
@ 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.
sumber
C # 7 akhirnya memberi kita cara yang elegan untuk melakukan ini:
sumber
Kenapa harus tahu dulu ?!
Cara termudah adalah menggunakan untuk bukannya foreach jika Anda menggunakan Daftar :
Atau jika Anda ingin menggunakan foreach:
Anda dapat menggunakan ini untuk mengetahui indeks setiap loop:
sumber
Ini akan berfungsi untuk mendukung koleksi
IList
.sumber
O(n^2)
karena dalam kebanyakan implementasiIndexOf
adalahO(n)
. 2) Ini gagal jika ada item duplikat dalam daftar.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.sumber
Jawaban ini: melobi tim bahasa C # untuk dukungan bahasa langsung.
Jawaban terkemuka menyatakan:
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
Jika
foreach ()
digunakan danwith var index
ada, maka kompiler mengharapkan koleksi item untuk menyatakanIIndexedEnumerable
antarmuka. Jika antarmuka tidak ada, kompiler dapat polyfill membungkus sumber dengan objek IndexedEnumerable, yang menambahkan kode untuk melacak indeks.Nantinya, CLR dapat diperbarui untuk memiliki pelacakan indeks internal, yang hanya digunakan jika
with
kata kunci ditentukan dan sumber tidak langsung menerapkanIIndexedEnumerable
Mengapa:
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
sumber
Jika koleksi adalah daftar, Anda dapat menggunakan List.IndexOf, seperti pada:
sumber
Lebih baik menggunakan
continue
konstruksi kata kunci yang aman seperti inisumber
Anda dapat menulis loop seperti ini:
Setelah menambahkan struct dan metode ekstensi berikut.
Metode struct dan ekstensi merangkum fungsionalitas Enumerable.Select.
sumber
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
sumber
struct
(indeks, item).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.
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 .
sumber
Saya tidak berpikir ini seharusnya cukup efisien, tetapi berfungsi:
sumber
Saya membangun ini di LINQPad :
Anda juga bisa menggunakan
string.join
:sumber
n
item maka operasin * n
, ataun
-squared. en.wikipedia.org/wiki/…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.
sumber
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.
sumber
Bagaimana dengan sesuatu yang seperti ini? Perhatikan bahwa myDelimitedString mungkin nol jika myEnumerable kosong.
sumber
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
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 ...
sumber
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.
Ini akan memberi Anda kamus dengan nama apakah item itu aneh (1) atau genap (0) dalam daftar.
sumber