for
vs. foreach
Bu iki yapının çok benzer olduğu ve her ikisinin de bunun gibi değiştirilebilir olduğu yönünde ortak bir karışıklık var:
foreach (var c in collection)
{
DoSomething(c);
}
ve:
for (var i = 0; i < collection.Count; i++)
{
DoSomething(collection[i]);
}
Her iki anahtar kelimenin de aynı üç harfle başlaması, anlamsal olarak benzer oldukları anlamına gelmez. Bu karışıklık özellikle yeni başlayanlar için son derece hataya açıktır. Bir koleksiyonda yineleme ve unsurlarla bir şeyler yapmak foreach
; for
Ne yaptığınızı gerçekten bilmiyorsanız, bu amaç için kullanılamaz ve kullanılmamalıdır .
Bir örnekle neyin yanlış olduğunu görelim. Sonunda, sonuçları toplamak için kullanılan bir demo uygulamasının tam kodunu bulacaksınız.
Örnekte, "Boston" ile karşılaşmadan önce veritabanına, daha doğrusu Adventure Works adlı şehirlere, adlarına göre sıralanan bazı verileri yüklüyoruz. Aşağıdaki SQL sorgusu kullanılır:
select distinct [City] from [Person].[Address] order by [City]
Veri ListCities()
bir döndüren yöntem tarafından yüklenir IEnumerable<string>
. İşte neye foreach
benziyor:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
Her for
ikisinin de değiştirilebilir olduğunu varsayarak, bunu bir ile yeniden yazalım:
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
Her ikisi de aynı şehirleri döndürür, ancak çok büyük bir fark vardır.
- Kullanırken
foreach
, ListCities()
bir zamanlar denir ve 47 madde verir.
- Kullanıldığında
for
, ListCities()
94 kez denir ve genel olarak 28153 ürün verir.
Ne oldu?
IEnumerable
olduğunu tembel . Bu, sadece sonucun gerekli olduğu anda işi yapacağı anlamına gelir. Tembel değerlendirme çok faydalı bir konsepttir, ancak sonucun gerekli olacağı anları, özellikle sonucun birden çok kez kullanıldığı durumlarda kaçırmanın kolay olduğu gerçeği gibi bazı uyarılar vardır.
Bir durumda foreach
, sonuç yalnızca bir kez istenir. Yukarıdaki yanlış yazılmış kodda uygulanan bir durum halinde , sonuç 94 kezfor
, yani 47x2 olarak talep edilir :
Bir veritabanını biri yerine 94 kez sorgulamak korkunç, ancak olabilecek en kötü şey değil. Örneğin, select
sorgudan önce tabloya bir satır ekleyen bir sorgudan önce gelirse ne olacağını hayal edin . Doğru, umarım daha önce çökmediği sürece for
veritabanını 2,147,483,647 kez arayacaktık .
Tabii ki kodum önyargılı. Ben tembelliğini kasıtlı olarak kullandım ve IEnumerable
tekrar tekrar arayacak şekilde yazdım ListCities()
. Bir aceminin bunu asla yapamayacağına dikkat çekilebilir, çünkü:
Özelliği IEnumerable<T>
değil Count
, yalnızca yöntem var Count()
. Bir yöntem çağırmak korkutucu ve sonuçta önbelleklenmemesi ve bir for (; ...; )
blokta uygun olmaması beklenebilir .
Dizin oluşturma için kullanılamıyor IEnumerable<T>
ve ElementAt
LINQ uzantı yöntemini bulmak açık değil .
Muhtemelen çoğu yeni başlayanlar ListCities()
, 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;
}
}
Yine de, bu kod foreach
alternatiften çok farklı . Yine, aynı sonuçları verir ve bu kez ListCities()
yöntem sadece bir kez çağrılır, ancak 575 öğe verirken foreach
, yalnızca 47 öğe verir.
Aradaki fark, tüm verilerin veritabanından yüklenmesine ToList()
neden olması gerçeğinden geliyor . İken "Boston" önce sadece şehirler talep, yeni alınan ve bellekte saklanmasını tüm şehirleri gerektirir. 575 kısa dizeyle büyük olasılıkla pek farketmez, fakat milyarlarca kayıt içeren bir tablodan sadece birkaç satır alıyor olsaydık ne olurdu?foreach
for
Peki foreach
gerçekte nedir?
foreach
bir süre döngüsüne daha yakın. Daha önce kullandığım kod:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
sadece tarafından değiştirilebilir:
using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
Her ikisi de aynı IL'yi üretir. Her ikisi de aynı sonuca sahiptir. Her ikisi de aynı yan etkiye sahiptir. Tabii ki, bu while
benzer bir sonsuzlukta yeniden yazılabilir for
, ancak daha uzun ve hataya açık olacaktır. Daha okunaklı bulduğunuz birini seçmekte özgürsünüz.
Kendin denemek ister misin? İşte tam kod:
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();
}
}
}
}
}
}
Ve sonuçlar:
--- için ---
Abingdon Albany İskenderiye Alhambra [...] Bonn Bordeaux Boston
Veriler 94 kez çağrıldı ve 28153 madde verdi.
--- liste için ile ---
Abingdon Albany İskenderiye Alhambra [...] Bonn Bordeaux Boston
Verilere 1 kez adı verildi ve 575 madde elde edildi.
--- iken ---
Abingdon Albany İskenderiye Elhamra [...] Bonn Bordeaux Boston
Verilere 1 kez adı verildi ve 47 madde verdi.
--- foreach ---
Abingdon Albany İskenderiye Elhamra [...] Bonn Bordeaux Boston
Verilere 1 kez adı verildi ve 47 madde verdi.
LINQ - geleneksel yolla
LINQ gelince, fonksiyonel programlama (FP) öğrenmek isteyebilirsiniz - C # FP şeyler değil, Haskell gibi gerçek FP dili. İşlevsel dillerin kodu ifade etmek ve sunmak için belirli bir yolu vardır. Bazı durumlarda, işlevsel olmayan paradigmalardan üstündür.
FP, listeleri manipüle etme konusunda çok daha üstün olduğu bilinmektedir ( listeyle ilgisi olmayan genel bir terim olarak listelenir List<T>
). Bu gerçeği göz önüne alındığında, C # kodunu listeler söz konusu olduğunda daha işlevsel bir şekilde ifade etme yeteneği oldukça iyi bir şeydir.
Eğer ikna olmadım söylüyorsanïz işlevsel ve işlevsel olmayan yollarla hem de yazılı kodun okunabilirliği karşılaştırmak önceki cevabı konuda.