
Expression Trees trong C#
March 4, 2025Cây biểu thức (Expression Trees) là một cấu trúc dữ liệu biểu diễn một biểu thức lambda dưới dạng cấu trúc cây. Chúng có thể được sử dụng để biểu diễn các truy vấn LINQ theo một cách trừu tượng và dễ dàng thao tác hơn.

Trong C#, cây biểu thức được tạo ra bằng cách sử dụng lớp Expression
và các kiểu dẫn xuất khác nhau của nó. Các lớp này đại diện cho nhiều loại biểu thức khác nhau như hằng số, biến và lời gọi phương thức. Ví dụ, đoạn mã sau đây tạo ra một cây biểu thức biểu diễn biểu thức “2 + 3”:
Expression<Func<int>> addExpression = () => 2 + 3;
Console.WriteLine(addExpression);
// Kết quả: "() => (2 + 3)"
Ở đây, chúng ta sử dụng lớp Expression<TDelegate>
để tạo một cây biểu thức đại diện cho biểu thức lambda () => 2 + 3
. Cây biểu thức này có thể được đánh giá để tạo ra kết quả của biểu thức, trong trường hợp này là 5.
Cây biểu thức có thể được sử dụng trong nhiều tình huống khác nhau, bao gồm:
- Biểu diễn các truy vấn LINQ theo cách trừu tượng, giúp dễ dàng thao tác và tối ưu hóa.
- Tạo truy vấn động trong thời gian chạy dựa trên đầu vào của người dùng hoặc các tiêu chí khác.
- Phân tích và sửa đổi các biểu thức lambda hoặc biểu thức khác trong thời gian chạy.
Ví dụ, đoạn mã sau tạo ra một cây biểu thức đại diện cho truy vấn LINQ lọc danh sách số để chỉ lấy các số lớn hơn 5:
IEnumerable<int> numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var query = from x in numbers where x > 5 select x;
Expression<Func<IEnumerable<int>>> expression = () => from x in numbers where x > 5 select x;
Console.WriteLine(expression);
Cây biểu thức này có thể dễ dàng được sửa đổi hoặc phân tích trong thời gian chạy, giúp tạo các truy vấn động hoặc tối ưu hóa truy vấn hiện có.
Ví dụ 1: Tạo truy vấn LINQ động
Cây biểu thức có thể được sử dụng để tạo các truy vấn LINQ động trong thời gian chạy dựa trên đầu vào của người dùng hoặc các tiêu chí khác. Ví dụ:
IEnumerable<Employee> employees = GetEmployees();
Expression<Func<Employee, bool>> agePredicate = e => e.Age > 25;
Expression<Func<IEnumerable<Employee>>> query = () => employees.Where(agePredicate);
foreach (var employee in query.Compile()())
{
Console.WriteLine(employee);
}
Ở đây, một cây biểu thức được sử dụng để biểu diễn truy vấn LINQ lọc danh sách nhân viên để chỉ lấy những người có độ tuổi lớn hơn 25. Phương thức Compile
được sử dụng để chuyển đổi cây biểu thức thành một delegate có thể thực thi để trả về kết quả của truy vấn.
Ví dụ 2: Phân tích và sửa đổi biểu thức
Cây biểu thức có thể được sử dụng để phân tích và sửa đổi các biểu thức lambda trong thời gian chạy. Ví dụ sau đây phân tích biểu thức lambda (x, y) => x + y
, sau đó sửa đổi nó để thực hiện phép nhân thay vì phép cộng:
Expression<Func<int, int, int>> addExpression = (x, y) => x + y;
foreach (var node in addExpression.Body.DescendantNodes())
{
Console.WriteLine($"Type: {node.NodeType}, Value: {node.ToString()}");
}
var parameter1 = Expression.Parameter(typeof(int), "x");
var parameter2 = Expression.Parameter(typeof(int), "y");
var multiplyExpression = Expression.Multiply(parameter1, parameter2);
var lambdaExpression = Expression.Lambda<Func<int, int, int>>(multiplyExpression, parameter1, parameter2);
var result = lambdaExpression.Compile()(3, 4);
Console.WriteLine(result); // Kết quả: "12"
Trong đoạn mã này, phương thức DescendantNodes
được sử dụng để duyệt qua các nút trong cây biểu thức và in ra loại và giá trị của từng nút. Sau đó, một cây biểu thức mới được tạo ra để thực hiện phép nhân thay vì phép cộng. Cuối cùng, biểu thức được biên dịch và thực thi để trả về kết quả.
Cây biểu thức là một tính năng hữu ích trong C# cho phép biểu diễn các biểu thức lambda và các biểu thức khác dưới dạng cấu trúc cây. Chúng có thể được sử dụng để biểu diễn các truy vấn LINQ theo cách trừu tượng, giúp dễ dàng thao tác và tối ưu hóa. Ngoài ra, cây biểu thức còn hữu ích trong việc tạo các truy vấn động trong thời gian chạy hoặc phân tích và sửa đổi các biểu thức trong thời gian thực.