Lời giải thích tốt nhất về cây biểu hiện mà tôi từng đọc là bài báo này của Charlie Calvert.
Tóm lại;
Cây biểu hiện đại diện cho những gì bạn muốn làm, không phải cách bạn muốn làm.
Hãy xem xét biểu thức lambda rất đơn giản sau:
Func<int, int, int> function = (a, b) => a + b;
Tuyên bố này bao gồm ba phần:
- Một tuyên bố:
Func<int, int, int> function
- Một toán tử bằng:
=
- Một biểu thức lambda:
(a, b) => a + b;
Biến function
trỏ tới mã thực thi thô biết cách cộng hai số .
Đây là sự khác biệt quan trọng nhất giữa đại biểu và biểu thức. Bạn gọi function
(a Func<int, int, int>
) mà không bao giờ biết nó sẽ làm gì với hai số nguyên bạn đã chuyển. Nó mất hai và trả về một, đó là hầu hết mã của bạn có thể biết.
Trong phần trước, bạn đã biết cách khai báo một biến trỏ đến mã thực thi thô. Cây biểu thức không phải là mã thực thi , chúng là một dạng cấu trúc dữ liệu.
Bây giờ, không giống như các đại biểu, mã của bạn có thể biết cây biểu thức được dùng để làm gì.
LINQ cung cấp một cú pháp đơn giản để dịch mã thành cấu trúc dữ liệu được gọi là cây biểu thức. Bước đầu tiên là thêm một câu lệnh using để giới thiệu vùng Linq.Expressions
tên:
using System.Linq.Expressions;
Bây giờ chúng ta có thể tạo một cây biểu thức:
Expression<Func<int, int, int>> expression = (a, b) => a + b;
Biểu thức lambda giống hệt được hiển thị trong ví dụ trước được chuyển đổi thành một cây biểu thức được khai báo là kiểu Expression<T>
. Mã định danh expression
không phải là mã thực thi; nó là một cấu trúc dữ liệu được gọi là cây biểu thức.
Điều đó có nghĩa là bạn không thể chỉ gọi một cây biểu thức như bạn có thể gọi một đại biểu, nhưng bạn có thể phân tích nó. Vì vậy, mã của bạn có thể hiểu gì bằng cách phân tích biến expression
?
var body = expression.Body;
var parameters = expression.Parameters;
var firstParam = parameters[0];
var secondParam = parameters[1].
Ở đây chúng ta thấy rằng có rất nhiều thông tin mà chúng ta có thể nhận được từ một biểu thức.
Nhưng tại sao chúng ta cần điều đó?
Bạn đã biết rằng cây biểu thức là một cấu trúc dữ liệu đại diện cho mã thực thi. Nhưng cho đến nay chúng tôi vẫn chưa trả lời được câu hỏi chính là tại sao một người lại muốn thực hiện một chuyển đổi như vậy. Đây là câu hỏi chúng tôi đã hỏi ở đầu bài viết này, và bây giờ là lúc để trả lời nó.
Truy vấn LINQ to SQL không được thực thi bên trong chương trình C # của bạn. Thay vào đó, nó được dịch sang SQL, được gửi qua một dây và thực thi trên máy chủ cơ sở dữ liệu. Nói cách khác, đoạn mã sau không bao giờ thực sự được thực thi bên trong chương trình của bạn:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };
Đầu tiên nó được dịch sang câu lệnh SQL sau và sau đó được thực thi trên máy chủ:
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0
Mã được tìm thấy trong một biểu thức truy vấn phải được dịch thành một truy vấn SQL có thể được gửi đến một quy trình khác dưới dạng một chuỗi. Trong trường hợp này, quá trình đó xảy ra là một cơ sở dữ liệu máy chủ SQL. Rõ ràng là sẽ dễ dàng hơn nhiều để dịch một cấu trúc dữ liệu như cây biểu thức sang SQL hơn là dịch IL thô hoặc mã thực thi sang SQL. Để phóng đại phần nào độ khó của vấn đề, chỉ cần tưởng tượng bạn đang cố gắng dịch một loạt các số không và số một sang SQL!
Khi đã đến lúc dịch biểu thức truy vấn của bạn sang SQL, cây biểu thức đại diện cho truy vấn của bạn sẽ được tách ra và phân tích, giống như chúng ta đã tách cây biểu thức lambda đơn giản của mình trong phần trước. Được cho là, thuật toán phân tích cú pháp cây biểu thức LINQ thành SQL phức tạp hơn nhiều so với thuật toán chúng ta đã sử dụng, nhưng nguyên tắc thì giống nhau. Khi nó đã phân tích các phần của cây biểu thức, LINQ nghiền ngẫm chúng và quyết định cách tốt nhất để viết một câu lệnh SQL sẽ trả về dữ liệu được yêu cầu.
Cây biểu thức được tạo ra để thực hiện nhiệm vụ chuyển đổi mã chẳng hạn như biểu thức truy vấn thành một chuỗi có thể được chuyển cho một số quy trình khác và thực hiện ở đó. Nó là đơn giản. Không có bí ẩn lớn nào ở đây, không có cây đũa thần nào cần phải vẫy. Người ta chỉ cần lấy mã, chuyển đổi nó thành dữ liệu và sau đó phân tích dữ liệu để tìm ra các phần cấu thành sẽ được dịch thành một chuỗi có thể được chuyển sang một quy trình khác.
Bởi vì truy vấn đến trình biên dịch được gói gọn trong một cấu trúc dữ liệu trừu tượng như vậy, trình biên dịch có thể tự do giải thích nó theo hầu hết mọi cách mà nó muốn. Nó không bị buộc phải thực hiện truy vấn theo một thứ tự cụ thể hoặc theo một cách cụ thể. Thay vào đó, nó có thể phân tích cây biểu thức, khám phá những gì bạn muốn làm và sau đó quyết định cách thực hiện. Ít nhất về lý thuyết, nó có quyền tự do xem xét bất kỳ yếu tố nào, chẳng hạn như lưu lượng mạng hiện tại, tải trên cơ sở dữ liệu, bộ kết quả hiện tại mà nó có sẵn, v.v. Trong thực tế, LINQ to SQL không xem xét tất cả các yếu tố này , nhưng về lý thuyết, nó là miễn phí để làm được khá nhiều thứ nó muốn. Hơn nữa, người ta có thể chuyển cây biểu thức này đến một số mã tùy chỉnh mà bạn viết bằng tay, mã này có thể phân tích và dịch nó thành một thứ gì đó rất khác với những gì được tạo bởi LINQ sang SQL.
Một lần nữa, chúng ta thấy rằng cây biểu thức cho phép chúng ta biểu diễn (thể hiện?) Những gì chúng ta muốn làm. Và chúng tôi sử dụng các trình dịch để quyết định cách diễn đạt của chúng tôi được sử dụng.