2013-07-03 8 views
5

मैं कई भागों में एक foreach पाश बंटवारे का एक तरीका की तलाश में किया गया है और निम्नलिखित कोड भर में आया था:Linq अनुकूलन

foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)) 
{ 
    //Do stuff 
} 

चाहेंगे items.Skip(currentPage * itemsPerPage).Take(itemsPerPage) हर यात्रा में संसाधित किया जा है, या यह संसाधित किया जाएगा एक बार, और संकलक द्वारा स्वचालित रूप से foreach पाश के साथ उपयोग किया गया एक अस्थायी परिणाम है?

+1

में एक को तोड़ने बिंदु रखो और देखते हैं। –

+0

यह केवल एक विभाजन है। क्या आप इसे लूप से भी बुला रहे हैं? –

उत्तर

6

foreach निर्माण के बराबर है।

अद्यतन:

कृपया ध्यान दें कि इस IEnumerator कि IEnumerable का उपयोग करता है के कार्यान्वयन पर निर्भर करता है।

इस (बुराई) उदाहरण में:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Collections; 


namespace TestStack 
{ 
    class EvilEnumerator<T> : IEnumerator<T> { 

     private IEnumerable<T> enumerable; 
     private int index = -1; 

     public EvilEnumerator(IEnumerable<T> e) 
     { 
      enumerable = e; 
     } 


     #region IEnumerator<T> Membres 

     public T Current 
     { 
      get { return enumerable.ElementAt(index); } 
     } 

     #endregion 

     #region IDisposable Membres 

     public void Dispose() 
     { 

     } 

     #endregion 

     #region IEnumerator Membres 

     object IEnumerator.Current 
     { 
      get { return enumerable.ElementAt(index); } 
     } 

     public bool MoveNext() 
     { 
      index++; 
      if (index >= enumerable.Count()) 
       return false; 
      return true; 
     } 

     public void Reset() 
     { 

     } 

     #endregion 
    } 
    class DemoEnumerable<T> : IEnumerable<T> 
    { 

     private IEnumerable<T> enumerable; 

     public DemoEnumerable(IEnumerable<T> e) 
     { 
      enumerable = e; 
     } 


     #region IEnumerable<T> Membres 

     public IEnumerator<T> GetEnumerator() 
     { 
      return new EvilEnumerator<T>(enumerable); 
     } 

     #endregion 

     #region IEnumerable Membres 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return this.GetEnumerator(); 
     } 

     #endregion 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      IEnumerable<int> numbers = Enumerable.Range(0,100); 
      DemoEnumerable<int> enumerable = new DemoEnumerable<int>(numbers); 
      foreach (var item in enumerable) 
      { 
       Console.WriteLine(item); 
      } 
     } 
    } 
} 

enumerable से अधिक प्रत्येक यात्रा numbers दो बार मूल्यांकन करेगा।

9

नहीं, यह एक बार संसाधित किया जाएगा।

यह वही की तरह है:

IEnumerator enumerator = myCollection.GetEnumerator(); 
try 
{ 
    while (enumerator.MoveNext()) 
    { 
     object current = enumerator.Current; 
     Console.WriteLine(current); 
    } 
} 
finally 
{ 
    IDisposable e = enumerator as IDisposable; 
    if (e != null) 
    { 
     e.Dispose(); 
    } 
} 

तो, नहीं, myCollection केवल एक बार संसाधित किया जाएगा:

public IEnumerable<Something> GetData() { 
    return someData; 
} 


foreach(var d in GetData()) { 
    //do something with [d] 
} 
+0

सुनिश्चित नहीं है कि यह सही है या नहीं। मेरा मतलब है कि आपका गेटडाटा फ़ंक्शन गेटटर केवल संपत्ति के समान है, और जब भी लूप अपना कदम बढ़ाता है, तो वह कॉलर प्राप्त करेगा, या आपके मामले में आपकी विधि, मूल रूप से प्रत्येक चरण पर छोड़ें/टेक निर्माण को कॉल करेगा। –

+0

@ पोटेकारू ट्यूडर: फोरैच लूप में, इसे एक कहा जाएगा। साबित करने के लिए, बस एक साधारण परीक्षण करें। – Tigran

+0

हाँ, बस एक परीक्षण उदाहरण डीबग किया और आप सही थे। धन्यवाद। –

0

प्रश्न:

चाहेंगे items.Skip (currentPage * itemsPerPage) .Take (itemsPerPage) हो हर यात्रा संसाधित, या यह एक बार संसाधित किया जाएगा, और एक अस्थायी foreach पाश के साथ प्रयोग किया परिणाम है स्वचालित रूप से संकलक द्वारा?

उत्तर:

यह, एक बार संसाधित किया जाएगा नहीं हर यात्रा। फोरच को अधिक पठनीय बनाने के लिए आप संग्रह को एक चर में डाल सकते हैं। नीचे इलस्ट्रेटेड

foreach(var item in items.Skip(currentPage * itemsPerPage).Take(itemsPerPage)) 
{ 
    //Do stuff 
} 

बनाम

List<MyClass> query = items.Skip(currentPage * itemsPerPage).Take(itemsPerPage).ToList(); 

foreach(var item in query) 
{ 
    //Do stuff 
} 

बनाम

IEnumerable<MyClass> query = items.Skip(currentPage * itemsPerPage).Take(itemsPerPage); 

foreach(var item in query) 
{ 
    //Do stuff 
} 
+1

मुझे कोड ब्लॉक के बीच एक लड़ाई दिखाई देती है .. –

+0

ऊपर संपादित किया गया। :) –

0

कोड है कि आप को पेश केवल सूची में आइटम्स पुनरावृति हो जाने के बाद, के रूप में अन्य लोगों ने बताया है।

हालांकि, यह आपको केवल एक पृष्ठ के लिए आइटम देता है। यदि आप एकाधिक पृष्ठों को प्रबंधित कर रहे हैं, तो आपको प्रत्येक पृष्ठ के लिए एक बार कोड को कॉल करना होगा (क्योंकि कहीं आपको currentPage बढ़ाना चाहिए, है ना?)।- प्रत्येक पृष्ठ के एक बार

for (int currentPage = 0; currentPage < numPages; ++currentPage) 
{ 
    foreach (var item in items.Skip(currentPage*itemsPerPage).Take(itemsPerPage)) 
    { 
     //Do stuff 
    } 
} 

अब अगर आप कि करते हैं, तो आप अनुक्रम कई बार पुनरावृत्ति कर दिया जाएगा:

मैं क्या मतलब है कि आप कुछ इस तरह कर रही किया जाना चाहिए है। पहला पुनरावृत्ति केवल पहले पृष्ठ के अंत तक ही जाएगा, लेकिन अगला शुरुआत से दूसरे पृष्ठ के अंत तक (Skip() और Take() के माध्यम से) होगा - और अगला शुरुआत से ही फिर से शुरू होगा तीसरे पृष्ठ के अंत। और इसी तरह।

इससे बचने के लिए आप IEnumerable<T> के लिए एक एक्सटेंशन विधि लिख सकते हैं जो बैच में डेटा को विभाजित करता है (जिसे आप "पृष्ठों" में डेटा को "पेजिंग" के रूप में भी वर्णित कर सकते हैं)।

सिर्फ IEnumerables की एक IEnumerable पेश करने के बजाय, यह एक कक्षा में प्रत्येक बैच रैप करने के लिए तो जैसे बैच में आइटम के साथ बैच सूचकांक आपूर्ति करने के लिए, और अधिक उपयोगी हो सकता है:

public sealed class Batch<T> 
{ 
    public readonly int Index; 
    public readonly IEnumerable<T> Items; 

    public Batch(int index, IEnumerable<T> items) 
    { 
     Index = index; 
     Items = items; 
    } 
} 

public static class EnumerableExt 
{ 
    // Note: Not threadsafe, so not suitable for use with Parallel.Foreach() or IEnumerable.AsParallel() 

    public static IEnumerable<Batch<T>> Partition<T>(this IEnumerable<T> input, int batchSize) 
    { 
     var enumerator = input.GetEnumerator(); 
     int index = 0; 

     while (enumerator.MoveNext()) 
      yield return new Batch<T>(index++, nextBatch(enumerator, batchSize)); 
    } 

    private static IEnumerable<T> nextBatch<T>(IEnumerator<T> enumerator, int blockSize) 
    { 
     do { yield return enumerator.Current; } 
     while (--blockSize > 0 && enumerator.MoveNext()); 
    } 
} 

इस विस्तार विधि डेटा को बफर नहीं करती है, और यह केवल एक बार इसके माध्यम से फिर से होती है।

इस विस्तार विधि को देखते हुए, यह आइटम को बैच करने के लिए और अधिक पठनीय हो जाता है। ध्यान दें कि यह उदाहरण के लिए, सभी पृष्ठों के लिए सभी वस्तुओं के माध्यम से विश्लेषण करता ओपी के उदाहरण जो केवल एक पृष्ठ के लिए आइटम के माध्यम से दोहराता के विपरीत:

var items = Enumerable.Range(10, 50); // Pretend we have 50 items. 
int itemsPerPage = 20; 

foreach (var page in items.Partition(itemsPerPage)) 
{ 
    Console.Write("Page " + page.Index + " items: "); 

    foreach (var i in page.Items) 
     Console.Write(i + " "); 

    Console.WriteLine(); 
}