2012-11-06 43 views
20

Tôi có một danh sách các mục và truy vấn LINQ trên chúng. Bây giờ, với thực thi hoãn lại của LINQ, vòng lặp tiếp theo sẽ thực hiện truy vấn chỉ một lần hoặc cho mỗi lượt trong vòng lặp?Foreach có thực hiện truy vấn chỉ một lần không?

Với ví dụ này (Trích từ Introduction to LINQ Queries (C#), on MSDN)

// The Three Parts of a LINQ Query: 
    // 1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery = 
     from num in numbers 
     where (num % 2) == 0 
     select num; 

    // 3. Query execution. 
    foreach (int num in numQuery) 
    { 
     Console.Write("{0,1} ", num); 
    } 

Hoặc, nói cách khác, sẽ có được bất kỳ sự khác biệt nếu tôi có:

foreach (int num in numQuery.ToList()) 

Và, nó sẽ có vấn đề, nếu dữ liệu cơ bản không nằm trong một mảng, nhưng trong một cơ sở dữ liệu?

Trả lời

15

Bây giờ, với thực thi trì hoãn của LINQ, vòng lặp tiếp theo sẽ thực hiện truy vấn chỉ một lần hoặc cho mỗi lượt trong vòng lặp?

Có, một lần cho vòng lặp. Trên thực tế, nó có thể thực hiện truy vấn ít hơn một lần - bạn có thể hủy bỏ phần vòng lặp thông qua và kiểm tra (num % 2) == 0 sẽ không được thực hiện trên bất kỳ mục nào còn lại.

Hoặc, nói cách khác, sẽ có bất kỳ sự khác biệt nếu tôi có:

foreach (int num in numQuery.ToList()) 

Hai khác biệt:

  1. Trong trường hợp trên, ToList() lãng phí thời gian và bộ nhớ , bởi vì nó đầu tiên thực hiện tương tự như số foreach ban đầu, tạo danh sách từ đó và sau đó là foreach s danh sách đó. Sự khác biệt sẽ ở đâu đó giữa tầm thường và ngăn chặn mã từ bao giờ làm việc, tùy thuộc vào kích thước của kết quả.

  2. Tuy nhiên, trong trường hợp bạn đang đi để nhiều lần làm foreach trên kết quả tương tự, hoặc nếu không sử dụng nó nhiều lần, sau đó trong khi foreach chỉ chạy các truy vấn một lần, tiếp theo foreach chạy nó một lần nữa. Nếu truy vấn là tốn kém, thì cách tiếp cận ToList() (và lưu trữ danh sách đó) có thể là một khoản tiết kiệm lớn.

+0

Có một lần. Có lẽ thậm chí ít hơn toàn bộ việc thực hiện truy vấn, vì bản chỉnh sửa ở trên giải thích. –

+0

Cảm ơn bạn đã giải thích chi tiết cho tôi! – Marcel

8

Không, nó không có sự khác biệt. Biểu thức in được đánh giá một lần. Cụ thể hơn, cấu trúc foreach gọi phương thức GetEnumerator() trên biểu thức in và nhiều lần gọi MoveNext() và truy cập thuộc tính Current để đi qua IEnumerable.

OTOH, gọi ToList() là không cần thiết. Bạn không nên bận tâm gọi nó.

Nếu đầu vào là một cơ sở dữ liệu, tình hình là hơi khác nhau, kể từ LINQ kết quả đầu ra IQueryable, nhưng tôi khá chắc chắn rằng foreach vẫn đối xử với nó như một IEnumerable (mà IQueryable kế thừa).

+0

... và đó là lý do tại sao ngoại lệ được ném khi cố thay đổi bộ sưu tập của 'bộ lọc '. – Alex

+0

@ Alex hoặc đúng, tại sao nó * có thể * được ném. Các quy tắc cho 'foreach' không hứa rằng việc sửa đổi sẽ được cho phép, nhưng các bộ sưu tập thì miễn phí cho phép, và đôi khi phải (những thứ được thiết kế để sử dụng đồng thời sẽ bị cản trở nếu chúng phải cấm thay đổi đồng thời từ các chủ đề khác). –

2

Khi được viết, mỗi lần lặp của vòng lặp sẽ thực hiện chính xác nhiều công việc cần thiết để tìm nạp kết quả tiếp theo. Vì vậy, câu trả lời về mặt kỹ thuật sẽ là "không có gì ở trên". Truy vấn sẽ thực thi "theo từng phần".

Nếu bạn sử dụng ToList() hoặc bất kỳ phương pháp thể hóa khác (ToArray() vv) thì truy vấn sẽ được đánh giá một lần tại chỗ và các hoạt động tiếp theo (như iterating qua kết quả) chỉ đơn giản là sẽ làm việc trên một danh sách "ngớ ngẩn".

Nếu numbersIQueryable thay vì IEnumerable - vì nó có thể nằm trong kịch bản cơ sở dữ liệu - thì ở trên vẫn gần với sự thật mặc dù không phải là mô tả chính xác hoàn hảo. Đặc biệt, trong lần thử đầu tiên để thực hiện một kết quả, nhà cung cấp có thể truy vấn sẽ nói chuyện với cơ sở dữ liệu và tạo ra một tập kết quả; sau đó, các hàng từ tập kết quả này sẽ được kéo trên mỗi lần lặp.

+0

Cảm ơn bạn đã giải thích phần DB. – Marcel

1

Truy vấn LINQ sẽ được thực hiện khi nó được liệt kê (hoặc như là kết quả của một cuộc gọi .ToList() hoặc làm một foreach so với kết quả.

Nếu bạn đang liệt kê các kết quả của truy vấn LINQ hai lần, cả hai lần sẽ làm cho nó truy vấn nguồn dữ liệu (trong ví dụ của bạn, liệt kê bộ sưu tập) vì nó tự nó chỉ trả về một số IEnumerable. Tuy nhiên, tùy thuộc vào truy vấn LINQ, nó có thể không luôn liệt kê toàn bộ bộ sưu tập (ví dụ: .Any().Single() sẽ dừng lại trên đối tượng đầu tiên hoặc đối tượng phù hợp đầu tiên nếu có .Where())

Chi tiết triển khai của nhà cung cấp LINQ có thể khác nhau do đó hành vi thông thường khi nguồn dữ liệu là cơ sở dữ liệu là gọi .ToList() ngay lập tức đến cache kết quả truy vấn & cũng đảm bảo truy vấn (trong trường hợp của EF, L2S hoặc NHibernate) được thực thi khi có và sau đó thay vì khi bộ sưu tập được liệt kê tại một số điểm sau đó trong mã và để ngăn truy vấn được thực thi nhiều lần nếu kết quả được liệt kê nhiều lần.

Các vấn đề liên quan