Pelatihan
Modul
Pelajari cara membuat variabel array dan melakukan iterasi melalui elemen array.
Browser ini sudah tidak didukung.
Mutakhirkan ke Microsoft Edge untuk memanfaatkan fitur, pembaruan keamanan, dan dukungan teknis terkini.
Hampir setiap program yang Anda tulis akan memiliki beberapa kebutuhan untuk melakukan iterasi atas koleksi. Anda akan menulis kode yang memeriksa setiap item dalam koleksi.
Anda juga akan membuat metode iterator, yang merupakan metode yang menghasilkan iterator untuk elemen kelas tersebut. Iterator adalah objek yang melintasi kontainer, terutama daftar. Iterator dapat digunakan untuk:
Bahasa C# menyediakan fitur untuk menghasilkan dan mengkonsumsi urutan. Urutan ini dapat diproduksi dan dikonsumsi secara sinkron atau asinkron. Artikel ini memberikan gambaran umum tentang fitur-fitur tersebut.
Menghitung koleksi sederhana: Kata foreach
kunci menghitung koleksi, menjalankan pernyataan yang disematkan sekali untuk setiap elemen dalam koleksi:
foreach (var item in collection)
{
Console.WriteLine(item?.ToString());
}
Itu saja. Untuk melakukan iterasi atas semua konten koleksi, foreach
pernyataan adalah semua yang Anda butuhkan. Pernyataan itu foreach
bukan sihir, meskipun. Ini bergantung pada dua antarmuka generik yang ditentukan dalam pustaka core .NET untuk menghasilkan kode yang diperlukan untuk mengulangi koleksi: IEnumerable<T>
dan IEnumerator<T>
. Mekanisme ini dijelaskan lebih rinci di bawah ini.
Kedua antarmuka ini juga memiliki rekan non-generik: IEnumerable
dan IEnumerator
. Versi generik lebih disukai untuk kode modern.
Ketika urutan dihasilkan secara asinkron, Anda dapat menggunakan await foreach
pernyataan untuk mengonsumsi urutan secara asinkron:
await foreach (var item in asyncSequence)
{
Console.WriteLine(item?.ToString());
}
Ketika urutannya adalah System.Collections.Generic.IEnumerable<T>, Anda menggunakan foreach
. Ketika urutannya adalah System.Collections.Generic.IAsyncEnumerable<T>, Anda menggunakan await foreach
. Dalam kasus terakhir, urutan dihasilkan secara asinkron.
Fitur hebat lain dari bahasa C# memungkinkan Anda membangun metode yang membuat sumber untuk enumerasi. Metode ini disebut sebagai metode iterator. Metode iterator menentukan cara menghasilkan objek secara berurutan saat diminta. Anda menggunakan yield return
kata kunci kontekstual untuk menentukan metode iterator.
Anda dapat menulis metode ini untuk menghasilkan urutan bilangan bulat dari 0 hingga 9:
public IEnumerable<int> GetSingleDigitNumbers()
{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}
Kode di atas menunjukkan pernyataan yang berbeda yield return
untuk menyoroti fakta bahwa Anda dapat menggunakan beberapa pernyataan diskrit yield return
dalam metode iterator. Anda dapat (dan sering melakukan) menggunakan konstruksi bahasa lain untuk menyederhanakan kode metode iterator. Definisi metode di bawah ini menghasilkan urutan angka yang sama persis:
public IEnumerable<int> GetSingleDigitNumbersLoop()
{
int index = 0;
while (index < 10)
yield return index++;
}
Anda tidak perlu memutuskan satu atau yang lain. Anda dapat memiliki pernyataan sebanyak yield return
yang diperlukan untuk memenuhi kebutuhan metode Anda:
public IEnumerable<int> GetSetsOfNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
index = 100;
while (index < 110)
yield return index++;
}
Semua contoh sebelumnya ini akan memiliki rekan asinkron. Dalam setiap kasus, Anda akan mengganti jenis IEnumerable<T>
pengembalian dengan IAsyncEnumerable<T>
. Misalnya, contoh sebelumnya akan memiliki versi asinkron berikut:
public async IAsyncEnumerable<int> GetSetsOfNumbersAsync()
{
int index = 0;
while (index < 10)
yield return index++;
await Task.Delay(500);
yield return 50;
await Task.Delay(500);
index = 100;
while (index < 110)
yield return index++;
}
Itulah sintaks untuk iterator sinkron dan asinkron. Mari kita pertimbangkan contoh dunia nyata. Bayangkan Anda berada di proyek IoT dan sensor perangkat menghasilkan aliran data yang sangat besar. Untuk merasakan data, Anda mungkin menulis metode yang mengambil sampel setiap elemen data Nth. Metode iterator kecil ini melakukan trik:
public static IEnumerable<T> Sample<T>(this IEnumerable<T> sourceSequence, int interval)
{
int index = 0;
foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Jika membaca dari perangkat IoT menghasilkan urutan asinkron, Anda akan memodifikasi metode seperti yang ditunjukkan metode berikut:
public static async IAsyncEnumerable<T> Sample<T>(this IAsyncEnumerable<T> sourceSequence, int interval)
{
int index = 0;
await foreach (T item in sourceSequence)
{
if (index++ % interval == 0)
yield return item;
}
}
Ada satu batasan penting pada metode iterator: Anda tidak dapat memiliki return
pernyataan dan yield return
pernyataan dalam metode yang sama. Kode berikut tidak akan dikompilasi:
public IEnumerable<int> GetSingleDigitNumbers()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
// generates a compile time error:
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
return items;
}
Pembatasan ini biasanya tidak menjadi masalah. Anda memiliki pilihan untuk menggunakan yield return
di seluruh metode, atau memisahkan metode asli menjadi beberapa metode, beberapa menggunakan return
, dan beberapa menggunakan yield return
.
Anda dapat memodifikasi metode terakhir sedikit untuk digunakan yield return
di mana-mana:
public IEnumerable<int> GetFirstDecile()
{
int index = 0;
while (index < 10)
yield return index++;
yield return 50;
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
foreach (var item in items)
yield return item;
}
Terkadang, jawaban yang tepat adalah membagi metode iterator menjadi dua metode yang berbeda. Salah satu yang menggunakan return
, dan detik yang menggunakan yield return
. Pertimbangkan situasi di mana Anda mungkin ingin mengembalikan koleksi kosong, atau lima angka ganjil pertama, berdasarkan argumen boolean. Anda dapat menulisnya sebagai dua metode ini:
public IEnumerable<int> GetSingleDigitOddNumbers(bool getCollection)
{
if (getCollection == false)
return new int[0];
else
return IteratorMethod();
}
private IEnumerable<int> IteratorMethod()
{
int index = 0;
while (index < 10)
{
if (index % 2 == 1)
yield return index;
index++;
}
}
Lihatlah metode di atas. Yang pertama menggunakan pernyataan standar return
untuk mengembalikan koleksi kosong, atau iterator yang dibuat oleh metode kedua. Metode kedua menggunakan yield return
pernyataan untuk membuat urutan yang diminta.
Pernyataan ini foreach
meluas ke idiom standar yang menggunakan IEnumerable<T>
antarmuka dan IEnumerator<T>
untuk melakukan iterasi di semua elemen koleksi. Ini juga meminimalkan kesalahan yang dilakukan pengembang dengan tidak mengelola sumber daya dengan benar.
Kompilator menerjemahkan perulangan yang foreach
ditunjukkan dalam contoh pertama menjadi sesuatu yang mirip dengan konstruksi ini:
IEnumerator<int> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
Kode yang tepat yang dihasilkan oleh pengkompilasi lebih rumit, dan menangani situasi di mana objek yang dikembalikan oleh GetEnumerator()
mengimplementasikan IDisposable
antarmuka. Ekspansi penuh menghasilkan kode lebih seperti ini:
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of enumerator.
}
}
Kompilator menerjemahkan sampel asinkron pertama menjadi sesuatu yang mirip dengan konstruksi ini:
{
var enumerator = collection.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
}
finally
{
// dispose of async enumerator.
}
}
Cara di mana enumerator dibuang tergantung pada karakteristik jenis enumerator
. Dalam kasus sinkron umum, finally
klausul diperluas ke:
finally
{
(enumerator as IDisposable)?.Dispose();
}
Kasus asinkron umum diperluas ke:
finally
{
if (enumerator is IAsyncDisposable asyncDisposable)
await asyncDisposable.DisposeAsync();
}
Namun, jika jenis adalah jenis enumerator
yang disegel dan tidak ada konversi implisit dari jenis enumerator
ke IDisposable
atau IAsyncDisposable
, finally
klausa meluas ke blok kosong:
finally
{
}
Jika ada konversi implisit dari jenis enumerator
ke, dan IDisposable
merupakan jenis nilai yang tidak dapat diubah ke null, enumerator
klausul akan diperluas finally
ke:
finally
{
((IDisposable)enumerator).Dispose();
}
Untungnya, Anda tidak perlu mengingat semua detail ini. Pernyataan ini foreach
menangani semua nuansa untuk Anda. Pengkompilasi akan menghasilkan kode yang benar untuk salah satu konstruksi ini.
Umpan balik .NET
.NET adalah proyek sumber terbuka. Pilih tautan untuk memberikan umpan balik:
Pelatihan
Modul
Pelajari cara membuat variabel array dan melakukan iterasi melalui elemen array.