THÔNG TIN CHI TIẾT
LINQ – Deferred operator và cơ chế thực hiện truy vấn  Cập nhật :16/10/2013  

Các toán tử (extension method) trong LINQ được chia thành hai loại theo cách mà chúng được thực thi: deferred và non-deferred. Việc hiểu mà phân biệt được các toán tử này là một vấn đề cần thiết vì bạn có thể nhận được một kết quả ngoài dự đoán khi sử dụng nếu không cẩn thận.

 

Các toán tử (extension method) trong LINQ được chia thành hai loại theo cách mà chúng được thực thi: deferred và non-deferred. Việc hiểu mà phân biệt được các toán tử này là một vấn đề cần thiết vì bạn có thể nhận được một kết quả ngoài dự đoán khi sử dụng nếu không cẩn thận.

 

 

Cơ chế thực hiện truy vấn và các deferred operator

Trước tiên bạn hãy xem một ví dụ đơn giản sau và xem kết quả được xuất ra:

 

01 public static void Main(){
02     List<string> list= new List<string>(){
03         "Olivia",
04         "Ruby",
05         "Sophie",
06     };
07  
08     IEnumerable<string> items = from n in list
09         where n.Length==4
10         select n;
11  
12     foreach(var item in items)
13         Console.WriteLine(item);
14  
15     Console.WriteLine("---------------");
16     list.Add("Lily");
17  
18     foreach(var item in items)
19         Console.WriteLine(item);
20  
21     Console.Read();
22 }

 

Output:

Ruby
—————
Ruby
Lily

Ví dụ trên dùng để xuất các tên có độ dài bằng 4. Như bạn thấy, mặc dù hoàn toàn không thay đổi gì liên quan đến biến items giữa hai lần xuất kết quả, nhưng kết quả xuất ra của hai lần lại khác nhau khi ta thêm một phần tử vào list.

Từ ví dụ này ta có thể suy ra mỗi lần được sử dụng, câu query sẽ được thực thi lại. Trong LINQ, khi bạn tạo một câu query và gán cho một đối tượng, thì lúc đó câu query này sẽ được lưu lại dưới dạng delegate Func<> trong một static field của lớp hiện tại, trong trường hợp này là Func<string,bool>.

Note: Để dễ thấy bạn có chuyển sang cú pháp viết bằng phương thức, tham số của phương thức Where() có thể là Func<T,bool> hoặc Func<T,int,bool>, với T là kiểu dữ liệu của các phần tử trong collection mà bạn sử dụng phương thức này (generic):

IEnumerable<string> items = list.Where( n => n.Length==4);

Trong LINQ, cơ chế này được gọi là “query plan” và các toán tử hoạt động theo cơ chế này được goi là “deferred operator”. Các toán tử còn lại được gọi là “non-deferred operator”.

Nhận biết các toán  tử này như thế, nào? Rất đơn giản, các toán tử deferred có kiểu trả về là IEnumerable<T> hoặc IOrderedEnumerable<T>

Có những trường hợp bạn muốn lấy toàn bộ kết quả của truy vấn, hãy sử dụng toán tử ToList,ToArray hoặc ToDictionary để chuyển chúng về một List<T> hoặc mảng. Bạn không phải lo lắng đến hiệu suất vì nghĩ rằng chúng sẽ thực hiện lặp lại nhiều lần để thực hiện truy vấn và tạo collection. Cơ chế thực hiện bên trong các toán tử  sẽ giúp tối ưu mà tôi sẽ bàn đến ngay trong phần sau đây.

Note: Func<> và IEnumerable<T> không phải là cách duy nhất mà LINQ sử dụng để lưu trữ và thực hiện truy vấn. Bạn sẽ được tìm hiểu về IQueryable<T> và lớp này sử dụng Expression Tree để lưu trữ câu truy vấn thay cho Func<>. Vấn đề này sẽ được giới thiệu trong một bài viết khác.

Cơ chế yielding và các toán tử trong LINQ

Chuyển qua một ví dụ khác ta có thể thấy cách mà phương thức Where() này được viết.

 

01 public static void Main(){
02     string[] numbers={"1","3","five"};
03     try{
04         IEnumerable<string> items =  from  n in numbers
05             where int.Parse(n)<5
06             select n;
07  
08         foreach(var item in items)
09             Console.WriteLine(item);
10  
11     }catch(Exception ex)
12     {
13         Console.WriteLine("Runtime exception: "+ex.Message);
14     }
15  
16     Console.Read();
17 }

 

Output:

1
3
Runtime exception: Input string was not in a correct format.

Câu lệnh thực hiện query trong ví dụ trên xảy ra lỗi do chuyển đổi chuỗi “five” thành một số Int32. Theo cách thông thường thì lẽ ra câu lệnh này phải ném ra ngoại lệ trước khi vòng lặp foreach được thực hiện. Tuy nhiên theo kết quả xuất ra, hai giá trị đầu hợp lệ là 1 và 3 vẫn được xuất ra màn hình.

Để làm được điều này, phương thức Where() sử dụng kĩ thuật yielding để trả về lần lượt từng phần tử khi nó được cần đến, thay vì thực hiện và trả về tất cả kết quả một lượt. Điều này giúp cho tốc độ thực hiện truy vấn nhanh hơn và kết quả có được gần như tức thời, đặc biệt hữu ích trong những trường hợp dữ liệu của bạn quá lớn.

Dựa vào đây ta có thể suy ra được nội dung của phương thức Where() tương tự như sau:

 

1 public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T,bool> predicate)
2 {
3     foreach(var item in source)
4     {
5         if(predicate(item))
6             yield return item;
7     }
8 }

 

Để kiểm chứng bạn có thể thực thi ví dụ sau, kết quả xuất ra tương tự như ví dụ trên:

 

01 using System;
02 using System.Collections.Generic;
03 using System.Linq;
04  
05 namespace LinqExample{
06     public static class Program
07     {
08         public static void Main(){
09             string[] numbers={"1","3","five"};
10             try{
11                 IEnumerable<string> items = numbers.Y2Where(n =>int.Parse(n) < 5);
12  
13                 foreach(var item in items)
14                     Console.WriteLine(item);
15  
16             }catch(Exception ex)
17             {
18                 Console.WriteLine("Runtime exception: "+ex.Message);
19             }
20  
21             Console.Read();
22         }
23  
24     }
25     public static class Y2QueryEx{
26         public static IEnumerable<T> Y2Where<T>(this IEnumerable<T> source,Func<T,bool> predicate)
27         {
28             foreach(var item in source)
29             {
30                 if(predicate(item))
31                     yield return item;
32             }
33         }
34     }
35 }

 

Output:

1
3
Runtime exception: Input string was not in a correct format.

Danh sách query operator theo thứ tự alphabet

By operator:

Operator Category

Deferred

Aggregate Aggregate  
All Quantifiers  
Any Quantifiers  
AsEnumerable Conversion

Yes

Average Aggregate  
Cast Conversion

Yes

Concat Concatenation

Yes

Contains Quantifiers  
Count Aggregate  
DefaultIfEmpty Element

Yes

Distinct Set

Yes

ElementAt Element  
ElementAtOrDefault Element

Yes

Empty Generation

Yes

Except Set

Yes

First Element  
FirstORDefault Element  
GroupBy Grouping

Yes

GroupJOin Join

Yes

Intersect Set

Yes

Join Join

Yes

Last Element  
LastOrDefault Element  
LongCount Aggregate  
Max Aggregate  
Min Aggregate  
OfType Conversion

Yes

OrderBy Ordering

Yes

OrderByDescending Ordering

Yes

Range Generation

Yes

Reverse Ordering

Yes

Repeat Generation

Yes

Select Projection

Yes

SelectMany Projection

Yes

SequenceEqual Equality  
Single Element  
SingleOrDefault Element  
Skip Partitioning

Yes

SkipWhile Partitioning

Yes

Sum Aggregate  
Take Partitioning

Yes

TakeWhile Partitioning

Yes

ThenBy Ordering

Yes

ThenByDescending Ordering

Yes

ToDictionary Conversion  
ToArray Conversion  
ToList Conversion  
ToLookup Conversion  
Union Set

Yes

Where Restriction

Yes

By category:

Category Operator

Deferred

Aggregate Aggregate  
Average  
Count  
LongCount  
Max  
Min  
Sum  
Concatenation Concat

Yes

Conversion AsEnumerable

Yes

Cast

Yes

Conversion OfType

Yes

ToDictionary  
ToArray  
ToList  
ToLookup  
Element DefaultIfEmpty

Yes

ElementAt  
ElementAtOrDefault

Yes

First  
FirstORDefault  
Last  
LastOrDefault  
Single  
SingleOrDefault  
Equality SequenceEqual  
Generation Empty

Yes

Range

Yes

Repeat

Yes

Grouping GroupBy

Yes

Join GroupJOin

Yes

Join

Yes

Ordering OrderBy

Yes

OrderByDescending

Yes

Reverse

Yes

ThenBy

Yes

ThenByDescending

Yes

Partitioning Skip

Yes

SkipWhile

Yes

Take

Yes

TakeWhile

Yes

Projection Select

Yes

SelectMany

Yes

Quantifiers All  
Any  
Contains  
Restriction Where

Yes

Set Distinct

Yes

Except

Yes

Intersect

Yes

Union

Yes

 

THÔNG TIN MỚI KHÁC
Những tai nạn "phòng the" có thể khiến bạn mất mạng -12/10/2019
Những sự thật về phương pháp tránh thai phổ biến nhất -28/09/2019
Vì sao đèn xi-nhan lại có màu da cam? -28/09/2019

Chia sẻ đến
THÔNG TIN