for
बनाम foreach
एक आम भ्रम है कि दो निर्माण बहुत समान हैं और दोनों इस तरह विनिमेय हैं:
foreach (var c in collection)
{
DoSomething(c);
}
तथा:
for (var i = 0; i < collection.Count; i++)
{
DoSomething(collection[i]);
}
यह तथ्य कि दोनों खोजशब्द एक ही तीन अक्षरों से शुरू होते हैं, इसका अर्थ यह नहीं है कि शब्दार्थ, वे समान हैं। यह भ्रम विशेष रूप से शुरुआती लोगों के लिए अत्यंत त्रुटिपूर्ण है। एक संग्रह के माध्यम से परिवर्तन करना और तत्वों के साथ कुछ करना foreach
; for
इस प्रयोजन के लिए उपयोग नहीं किया जाना चाहिए और तब तक नहीं किया जाना चाहिए , जब तक कि आप वास्तव में नहीं जानते कि आप क्या कर रहे हैं।
आइए देखें कि इसमें एक उदाहरण के साथ क्या गलत है। अंत में, आपको परिणामों को इकट्ठा करने के लिए उपयोग किए जाने वाले डेमो एप्लिकेशन का पूरा कोड मिलेगा।
उदाहरण में, हम "बोस्टन" का सामना करने से पहले, डेटाबेस से कुछ डेटा लोड कर रहे हैं, एडवेंचर वर्क्स के शहरों से, नाम से आदेश दिया गया है। निम्न SQL क्वेरी का उपयोग किया जाता है:
select distinct [City] from [Person].[Address] order by [City]
डेटा ListCities()
विधि द्वारा लोड किया जाता है जो एक रिटर्न देता है IEnumerable<string>
। यहाँ जैसा foreach
दिखता है वैसा है:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
चलो इसे फिर से लिखते हैं for
, यह मानते हुए कि दोनों विनिमेय हैं:
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
दोनों एक ही शहर लौटते हैं, लेकिन बहुत बड़ा अंतर है।
- उपयोग करते समय
foreach
, ListCities()
एक समय कहा जाता है और 47 वस्तुओं की पैदावार होती है।
- उपयोग करते समय
for
, ListCities()
94 बार कहा जाता है और कुल मिलाकर 28153 वस्तुओं की पैदावार होती है।
क्या हुआ?
IEnumerable
है आलसी । इसका मतलब है कि यह उस समय ही काम करेगा जब परिणाम की आवश्यकता होगी। आलसी मूल्यांकन एक बहुत ही उपयोगी अवधारणा है, लेकिन इसमें कुछ तथ्य भी शामिल हैं, जिसमें इस तथ्य को याद करना आसान है कि परिणाम को याद करना आसान है, जहां परिणाम की आवश्यकता होगी, विशेष रूप से उन मामलों में जहां परिणाम कई बार उपयोग किया जाता है।
एक मामले में foreach
, परिणाम केवल एक बार अनुरोध किया जाता है। उपरोक्त लिखित गलत कोड के रूप में कार्यान्वित होने की स्थिति में , परिणाम 94 बार अनुरोध किया जाता हैfor
, अर्थात 47 × 2:
एक के बजाय 94 बार डेटाबेस को छोड़ना भयानक है, लेकिन इससे भी बदतर चीज जो हो सकती है। उदाहरण के लिए, उदाहरण के लिए, यदि select
क्वेरी उस क्वेरी से पहले होगी, जो तालिका में एक पंक्ति सम्मिलित करती है , तो क्या होगा । ठीक है, हमारे पास for
डेटाबेस को 2,147,483,647 बार कॉल करना होगा , जब तक कि यह उम्मीद से पहले दुर्घटनाग्रस्त न हो जाए।
बेशक, मेरा कोड पक्षपाती है। मैंने जानबूझकर आलस का इस्तेमाल किया IEnumerable
और इसे बार-बार बुलाने के तरीके से लिखा ListCities()
। एक नोट कर सकता है कि एक शुरुआतकर्ता ऐसा कभी नहीं करेगा, क्योंकि:
IEnumerable<T>
संपत्ति नहीं है Count
, लेकिन केवल विधि Count()
। एक विधि को कॉल करना डरावना है, और कोई भी इसके परिणाम को कैश नहीं किया जा सकता है, और for (; ...; )
ब्लॉक में उपयुक्त नहीं होने की उम्मीद कर सकता है ।
अनुक्रमण के लिए अनुपलब्ध है IEnumerable<T>
और ElementAt
LINQ एक्सटेंशन विधि को खोजने के लिए स्पष्ट नहीं है ।
संभवतया अधिकांश शुरुआती सिर्फ उसी ListCities()
चीज के परिणाम को परिवर्तित करेंगे जिसके साथ वे परिचित हैं, जैसे 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;
}
}
फिर भी, यह कोड foreach
विकल्प से बहुत अलग है । फिर से, यह एक ही परिणाम देता है, और इस बार ListCities()
विधि को केवल एक बार कहा जाता है, लेकिन 575 वस्तुओं की पैदावार होती है, जबकि इसके साथ foreach
, यह 47 वस्तुओं का उत्पादन करता है।
अंतर इस तथ्य से आता है कि डेटाबेस से सभी डेटा लोड होने का ToList()
कारण बनता है। जबकि "बोस्टन" से पहले केवल शहरों का अनुरोध किया गया था, नए को सभी शहरों को पुनः प्राप्त करने और स्मृति में संग्रहीत करने की आवश्यकता है। 575 छोटी स्ट्रिंग्स के साथ, शायद इससे बहुत फर्क नहीं पड़ता है, लेकिन क्या होगा अगर हम अरबों के रिकॉर्ड वाली तालिका से केवल कुछ पंक्तियों को पुनः प्राप्त कर रहे थे?foreach
for
तो foreach
वास्तव में क्या है ?
foreach
थोड़ी देर के पाश के करीब है। कोड जो मैंने पहले इस्तेमाल किया था:
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
बस द्वारा प्रतिस्थापित किया जा सकता है:
using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
दोनों एक ही आईएल का उत्पादन करते हैं। दोनों का परिणाम समान है। दोनों का एक ही साइड इफेक्ट है। बेशक, यह while
एक समान अनंत में फिर से लिखा जा सकता है for
, लेकिन यह अधिक लंबा और त्रुटि-प्रवण होगा। आप अधिक पढ़ने योग्य खोजने के लिए स्वतंत्र हैं।
इसे स्वयं परखना चाहते हैं? यहाँ पूर्ण कोड है:
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();
}
}
}
}
}
}
और परिणाम:
--- के लिए ---
अबिंगडन अल्बानी अलेक्जेंड्रिया अल्हाम्ब्रा [...] बॉन बोर्डो बोस्टन
डेटा को 94 समय (सेकंड) कहा जाता था और 28153 आइटम (आइटम) प्राप्त होते थे।
--- सूची के साथ ---
अबिंगडन अल्बानी अलेक्जेंड्रिया अल्हाम्ब्रा [...] बॉन बोर्डो बोस्टन
डेटा को 1 बार (s) कहा गया और 575 आइटम (s) प्राप्त हुए।
--- जबकि ---
एबिंगडन अल्बानी अलेक्जेंड्रिया अल्हाम्ब्रा [...] बॉन बोर्डो बोस्टन
डेटा को 1 बार (s) कहा गया और 47 आइटम (s) प्राप्त हुए।
--- फ़ॉरच्यू ---
अबिंगडन अल्बानी अलेक्जेंड्रिया अल्हाम्ब्रा [...] बॉन बोर्डो बोस्टन
डेटा को 1 बार (s) कहा गया और 47 आइटम (s) प्राप्त हुए।
LINQ बनाम पारंपरिक तरीका
LINQ के लिए, आप कार्यात्मक प्रोग्रामिंग (FP) सीखना चाहते हैं - C # FP सामान नहीं, बल्कि असली FP भाषा जैसे Haskell। कार्यात्मक भाषाओं में कोड को व्यक्त करने और प्रस्तुत करने का एक विशिष्ट तरीका है। कुछ स्थितियों में, यह गैर-कार्यात्मक प्रतिमानों से बेहतर है।
जब सूची में हेरफेर करने की बात आती है, तो एफपी बहुत बेहतर माना जाता है ( सामान्य शब्द के रूप में सूची , असंबंधित List<T>
)। इस तथ्य को देखते हुए, जब यह सूचियों की बात आती है तो C # कोड को अधिक कार्यात्मक तरीके से व्यक्त करने की क्षमता बल्कि एक अच्छी बात है।
यदि आप आश्वस्त नहीं हैं, तो विषय पर मेरे पिछले उत्तर में कार्यात्मक और गैर-कार्यात्मक दोनों तरीकों से लिखे गए कोड की पठनीयता की तुलना करें ।