2013-07-03 74 views
5

tôi đã được tìm kiếm một cách để tách một vòng lặp foreach thành nhiều bộ phận và đi qua đoạn mã sau:LINQ tối ưu hóa trong một foreach

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

Would items.Skip(currentPage * itemsPerPage).Take(itemsPerPage) được xử lý trong mỗi lần lặp, hoặc nó sẽ được xử lý một lần, và có một kết quả tạm thời được sử dụng với vòng lặp foreach tự động bởi trình biên dịch?

+1

Đặt điểm ngắt và xem. –

+0

Đó chỉ là một phần. Bạn đang gọi đó từ một vòng lặp quá? –

Trả lời

6

Việc xây dựng foreach là tương đương với:

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

Vì vậy, không, myCollection sẽ được xử lý chỉ một lần.

Cập nhật:

Xin lưu ý rằng điều này phụ thuộc vào việc thực hiện IEnumerator rằng IEnumerable sử dụng.

In (ác) này ví dụ:

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

Mỗi lần lặp trên enumerable sẽ đánh giá numbers hai lần.

9

Không, nó sẽ được xử lý một lần.

Đó là cùng như:

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


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

Không chắc chắn nếu điều này là chính xác. Tôi có nghĩa là hàm GetData của bạn tương tự như một thuộc tính getter và mỗi lần lặp lại bước của nó, sẽ gọi đó là accessor, hoặc trong trường hợp của bạn, về cơ bản gọi Skip/Take xây dựng trên mỗi bước. –

+0

@PotecaruTudor: trong vòng lặp foreach, nó sẽ được gọi là những cái. Để chứng minh điều đó, chỉ cần làm một bài kiểm tra đơn giản. – Tigran

+0

Đúng, chỉ cần gỡ lỗi một ví dụ thử nghiệm và bạn đã đúng. Cảm ơn. –

0

Câu hỏi:

Would items.Skip (currentPage * itemsPerPage) .Take (itemsPerPage) được xử lý mỗi lần lặp, hoặc nó sẽ được xử lý một lần, và có một kết quả tạm thời sử dụng với các vòng lặp foreach tự động bởi trình biên dịch ?

Trả lời:

Nó sẽ được xử lý một lần, không phải mọi lặp. Bạn có thể đưa bộ sưu tập vào một biến để làm cho việc đọc trở nên dễ đọc hơn. Minh họa dưới đây.

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

vs

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

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

vs

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

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

Tôi thấy một trận chiến giữa các khối mã .. –

+0

Đã chỉnh sửa ở trên. :) –

0

Các mã mà bạn trình bày sẽ chỉ lặp các mục trong danh sách một lần, như những người khác đã chỉ ra.

Tuy nhiên, chỉ cung cấp cho bạn các mục cho một trang. Nếu bạn đang xử lý nhiều trang, bạn phải gọi mã đó một lần cho mỗi trang (vì một nơi nào đó bạn phải tăng thêm currentPage, phải không?).

Những gì tôi có nghĩa là bạn phải làm gì đó như thế này:

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

Bây giờ nếu bạn làm rằng, sau đó bạn sẽ được lặp lại trình tự nhiều lần - một lần cho mỗi trang. Lần lặp đầu tiên sẽ chỉ đi xa đến cuối trang đầu tiên, nhưng lần tiếp theo sẽ lặp lại từ đầu đến cuối trang thứ hai (thông qua Skip()Take()) - và bước tiếp theo sẽ lặp lại từ đầu đến cuối trang thứ ba. Và cứ thế.

Để tránh điều đó, bạn có thể viết một phương pháp mở rộng cho IEnumerable<T> để phân chia dữ liệu thành các lô (bạn cũng có thể mô tả là "phân trang" dữ liệu thành "trang").

Thay vì chỉ trình bày một IEnumerable của IEnumerables, nó có thể hữu ích hơn để quấn từng lô trong một lớp học để cung cấp các chỉ số hàng loạt cùng với các mục trong hàng loạt, như vậy:

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

Phần mở rộng này phương pháp không đệm dữ liệu, và nó chỉ lặp qua nó một lần.

Với phương pháp mở rộng này, nó trở nên dễ đọc hơn để sắp xếp các mục. Lưu ý rằng ví dụ này liệt kê qua TẤT CẢ các mục cho tất cả các trang, không giống như ví dụ của OP mà chỉ lặp qua các mục cho một trang:

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