2013-07-03 3 views
5

Я искал способ расщепления foreach петлю на несколько частей и наткнулся на следующий код:оптимизация Linq в Еогеасп

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

Would items.Skip(currentPage * itemsPerPage).Take(itemsPerPage) обрабатываться в каждой итерации, или он будет обработан один раз, и иметь временный результат, используемый с циклом foreach автоматически компилятором?

+1

Поместите точку останова и посмотрите. –

+0

Это только один раскол. Вы тоже звоните из цикла? –

ответ

6

Конструкция Еогеасп эквивалентно:

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 будет обрабатываться только один раз.

Update:

Пожалуйста, обратите внимание, что это зависит от реализации 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

Нет, он будет обработан один раз.

Это же как:

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


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

Не уверен, что это правильно. Я имею в виду, что ваша функция GetData похожа на свойство только для getter, и каждый раз, когда цикл увеличивает шаг, вызовет этот метод get accessor или в вашем случае ваш метод, в основном вызывающий конструкцию Skip/Take на каждом шаге. –

+0

@PotecaruTudor: в цикле foreach он будет называться. Чтобы доказать это, просто сделайте простой тест. – Tigran

+0

Да, просто отладил тестовый пример, и вы были правы. Благодарю. –

0

Вопрос:

Would items.Skip (CurrentPage * itemsPerPage) .Снять (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>, который разбивает данные на партии (которые вы также можете описать как «разбиение на страницы» данных на «страницы»).

Вместо того, чтобы просто представляя IEnumerable из IEnumerables, он может быть более полезным, чтобы обернуть каждую партию в классе, чтобы поставить индекс партии вместе с деталями в пакете, например, так:

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()); 
    } 
} 

Это расширение метод не буферизует данные, и он только через него проходит через него один раз.

Учитывая этот метод расширения, он становится более читаемым для пакетной обработки элементов. Обратите внимание, что в этом примере перечислены все элементы для всех страниц, в отличие от примера OP, который выполняет только итерации через элементы для одной страницы:

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(); 
} 
Смежные вопросы