for
vs. foreach
Ada kebingungan umum bahwa kedua konstruksi ini sangat mirip dan keduanya dapat dipertukarkan seperti ini:
foreach (var c in collection)
{
DoSomething(c);
}
dan:
for (var i = 0; i < collection.Count; i++)
{
DoSomething(collection[i]);
}
Fakta bahwa kedua kata kunci dimulai dengan tiga huruf yang sama tidak berarti secara semantik, keduanya serupa. Kebingungan ini sangat rawan kesalahan, terutama untuk pemula. Iterasi melalui koleksi dan melakukan sesuatu dengan elemen dilakukan dengan foreach
; for
tidak harus dan tidak boleh digunakan untuk tujuan ini , kecuali jika Anda benar-benar tahu apa yang Anda lakukan.
Mari kita lihat apa yang salah dengan contoh itu. Pada akhirnya, Anda akan menemukan kode lengkap aplikasi demo yang digunakan untuk mengumpulkan hasil.
Dalam contoh, kami memuat beberapa data dari basis data, lebih tepatnya kota-kota dari Adventure Works, yang dipesan dengan nama, sebelum bertemu "Boston". Query SQL berikut digunakan:
select distinct [City] from [Person].[Address] order by [City]
Data dimuat oleh ListCities()
metode yang mengembalikan sebuah IEnumerable<string>
. Berikut ini foreach
tampilannya:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
Mari kita menulis ulang dengan a for
, dengan asumsi bahwa keduanya dapat dipertukarkan:
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
Keduanya mengembalikan kota yang sama, tetapi ada perbedaan besar.
- Saat menggunakan
foreach
, ListCities()
disebut satu kali dan menghasilkan 47 item.
- Saat menggunakan
for
, ListCities()
disebut 94 kali dan menghasilkan 28153 item secara keseluruhan.
Apa yang terjadi?
IEnumerable
adalah malas . Ini berarti bahwa itu akan melakukan pekerjaan hanya pada saat ketika hasilnya dibutuhkan. Evaluasi malas adalah konsep yang sangat berguna, tetapi memiliki beberapa peringatan, termasuk fakta bahwa mudah untuk melewatkan momen di mana hasilnya akan diperlukan, terutama dalam kasus di mana hasilnya digunakan beberapa kali.
Dalam kasus a foreach
, hasilnya diminta hanya sekali. Dalam kasus a for
sebagaimana diterapkan dalam kode yang ditulis secara tidak benar di atas , hasilnya diminta 94 kali , yaitu 47 × 2:
Meminta basis data 94 kali alih-alih yang mengerikan, tetapi bukan hal terburuk yang mungkin terjadi. Bayangkan, misalnya, apa yang akan terjadi jika select
kueri akan didahului oleh kueri yang juga menyisipkan baris dalam tabel. Benar, kita akan memiliki for
yang akan memanggil database 2.147.483.647 kali, kecuali semoga crash sebelumnya.
Tentu saja, kode saya bias. Saya sengaja menggunakan kemalasan IEnumerable
dan menulisnya dengan cara menelepon berulang kali ListCities()
. Orang dapat mencatat bahwa seorang pemula tidak akan pernah melakukan itu, karena:
Tidak IEnumerable<T>
memiliki properti Count
, tetapi hanya metodenya Count()
. Memanggil metode itu menakutkan, dan orang bisa berharap hasilnya tidak akan di-cache, dan tidak cocok di for (; ...; )
blok.
Pengindeksan tidak tersedia untuk IEnumerable<T>
dan tidak jelas untuk menemukan ElementAt
metode ekstensi LINQ.
Mungkin sebagian besar pemula hanya akan mengubah hasil ListCities()
menjadi sesuatu yang mereka kenal, seperti a List<T>
.
var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
var city = flushedCities[i];
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
Meski begitu, kode ini sangat berbeda dari foreach
alternatif. Sekali lagi, ini memberikan hasil yang sama, dan kali ini ListCities()
metode ini dipanggil hanya sekali, tetapi menghasilkan 575 item, sementara dengan foreach
, itu hanya menghasilkan 47 item.
Perbedaannya berasal dari fakta yang ToList()
menyebabkan semua data dimuat dari basis data. Meskipun foreach
hanya meminta kota sebelum "Boston", yang baru for
meminta semua kota untuk diambil dan disimpan dalam memori. Dengan 575 string pendek, mungkin tidak ada banyak perbedaan, tetapi bagaimana jika kita mengambil hanya beberapa baris dari tabel yang berisi milyaran catatan?
Jadi apa foreach
sebenarnya?
foreach
lebih dekat ke loop sementara. Kode yang saya gunakan sebelumnya:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
dapat dengan mudah diganti dengan:
using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
Keduanya menghasilkan IL yang sama. Keduanya memiliki hasil yang sama. Keduanya memiliki efek samping yang sama. Tentu saja, ini while
dapat ditulis ulang dalam infinite yang serupa for
, tetapi akan lebih lama dan rawan kesalahan. Anda bebas memilih yang menurut Anda lebih mudah dibaca.
Ingin mengujinya sendiri? Berikut kode lengkapnya:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
public class Program
{
private static int countCalls;
private static int countYieldReturns;
public static void Main()
{
Program.DisplayStatistics("for", Program.UseFor);
Program.DisplayStatistics("for with list", Program.UseForWithList);
Program.DisplayStatistics("while", Program.UseWhile);
Program.DisplayStatistics("foreach", Program.UseForEach);
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
}
private static void DisplayStatistics(string name, Action action)
{
Console.WriteLine("--- " + name + " ---");
Program.countCalls = 0;
Program.countYieldReturns = 0;
var measureTime = Stopwatch.StartNew();
action();
measureTime.Stop();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("The data was called {0} time(s) and yielded {1} item(s) in {2} ms.", Program.countCalls, Program.countYieldReturns, measureTime.ElapsedMilliseconds);
Console.WriteLine();
}
private static void UseFor()
{
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseForWithList()
{
var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
var city = flushedCities[i];
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseForEach()
{
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseWhile()
{
using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
}
private static IEnumerable<string> ListCities()
{
Program.countCalls++;
using (var connection = new SqlConnection("Data Source=mframe;Initial Catalog=AdventureWorks;Integrated Security=True"))
{
connection.Open();
using (var command = new SqlCommand("select distinct [City] from [Person].[Address] order by [City]", connection))
{
using (var reader = command.ExecuteReader(CommandBehavior.SingleResult))
{
while (reader.Read())
{
Program.countYieldReturns++;
yield return reader["City"].ToString();
}
}
}
}
}
}
Dan hasilnya:
--- untuk ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston
Data disebut 94 kali dan menghasilkan 28153 item.
--- untuk daftar ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston
Data disebut 1 kali dan menghasilkan 575 item.
--- sementara ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston
Data disebut 1 kali dan menghasilkan 47 item.
--- pendahuluan ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston
Data disebut 1 kali dan menghasilkan 47 item.
LINQ vs cara tradisional
Adapun LINQ, Anda mungkin ingin belajar pemrograman fungsional (FP) - bukan hal C # FP, tetapi bahasa FP nyata seperti Haskell. Bahasa fungsional memiliki cara khusus untuk mengekspresikan dan menyajikan kode. Dalam beberapa situasi, ini lebih unggul dari paradigma non-fungsional.
FP diketahui jauh lebih unggul dalam hal memanipulasi daftar ( daftar sebagai istilah umum, yang tidak terkait List<T>
). Mengingat fakta ini, kemampuan untuk mengekspresikan kode C # dengan cara yang lebih fungsional ketika datang ke daftar agak bagus.
Jika Anda tidak yakin, bandingkan dengan keterbacaan kode yang ditulis dengan cara fungsional dan non-fungsional dalam jawaban saya sebelumnya pada subjek.