Xây dựng Biểu Thức Logic Động trong C#

Xây dựng Biểu Thức Logic Động trong C#

September 15, 2025 0 By Nam Vu

Là một lập trình viên C#, bạn có thể đã từng gặp các tình huống cần xây dựng những biểu thức logic phức tạp một cách động dựa trên đầu vào từ người dùng hoặc các yếu tố thay đổi trong thời gian chạy. Trong những trường hợp như vậy, việc viết biểu thức theo cách tĩnh có thể trở nên tẻ nhạt và dễ xảy ra lỗi.

Bài viết này sẽ hướng dẫn bạn cách sử dụng Expression Trees để xây dựng các biểu thức logic trong C#. Chúng ta sẽ bắt đầu bằng một ví dụ đơn giản kết hợp nhiều điều kiện với các toán tử AndOr. Sau đó, chúng ta sẽ tìm hiểu cách ưu tiên thực hiện giữa các toán tử này để tối ưu biểu thức.

Trong ví dụ, chúng ta sẽ làm việc với một danh sách các giá trị boolean và một tập hợp các yêu cầu logic. Mỗi yêu cầu logic xác định một giá trị boolean và một toán tử logic (“AND” hoặc “OR”). Mục tiêu là xây dựng một biểu thức logic áp dụng các toán tử này lên các giá trị trong danh sách.

Expression Trees trong C#

Expression Trees là một tính năng mạnh mẽ trong C# cho phép lập trình viên biểu diễn mã lệnh dưới dạng cấu trúc dữ liệu dạng cây. Mỗi nút trong cây thể hiện một phần của đoạn mã hoặc một phép toán.

Tính năng này đặc biệt hữu ích trong các kịch bản cần xây dựng mã lệnh động khi chạy chương trình, như tạo câu truy vấn, thao tác với cơ sở dữ liệu hoặc thực hiện phân tích và tối ưu mã.

Lợi ích của Expression Trees:

  1. Sinh mã động: Cho phép xây dựng các truy vấn, thao tác cơ sở dữ liệu và đoạn mã tùy chỉnh khi runtime.
  2. Phân tích mã: Có thể dùng để kiểm tra, nhận diện mẫu và tối ưu hóa mã lệnh.
  3. Chuyển đổi mã: Hỗ trợ biến đổi từ biểu thức này sang dạng khác, ví dụ từ truy vấn LINQ thành SQL.
  4. Kiểu dữ liệu mạnh mẽ: Biểu thức có thể kiểm tra kiểu tại thời điểm biên dịch để đảm bảo tính hợp lệ.
  5. Cú pháp quen thuộc: Gần giống với C# nên dễ học và dễ sử dụng.

Hạn chế của Expression Trees:

  1. Độ phức tạp cao: Nhất là với biểu thức lồng nhau, gây khó hiểu và khó debug.
  2. Ảnh hưởng hiệu suất: Việc tạo và thực thi mã động có thể ảnh hưởng đến hiệu năng nếu thực hiện thường xuyên.
  3. Đòi hỏi kiến thức: Cần hiểu sâu về khái niệm để triển khai hiệu quả.

Kết hợp nhiều điều kiện

Giả sử chúng ta có một danh sách các đối tượng LogicRequest, mỗi đối tượng gồm một giá trị bool và một kiểu LogicType (AND/OR). Nhiệm vụ là xây dựng một biểu thức logic trả về true nếu tất cả điều kiện đúng (AND), hoặc ít nhất một điều kiện đúng (OR).

public class LogicRequest
{
    public bool Result { get; set; }
    public LogicType Type { get; set; }
}

public enum LogicType
{
    And,
    Or
}

Khởi tạo danh sách dữ liệu mẫu:

var boolList = new List<bool>();

var logicRequests = new List<LogicRequest>
{
    new LogicRequest { Result = true, Type = LogicType.And },
    new LogicRequest { Result = false, Type = LogicType.Or },
    new LogicRequest { Result = true, Type = LogicType.And }
};

Tạo ParameterExpression để đại diện cho đầu vào của biểu thức:

var inputs = new List<ParameterExpression>();
var input = Expression.Parameter(typeof(List<bool>), "input");
inputs.Add(input);

Truy xuất giá trị từng phần tử qua Expression.MakeIndex:

var results = new List<Expression>();
var count = 0;
foreach (var logicRequest in logicRequests)
{
    boolList.Add(logicRequest.Result);
    var indexAccess = Expression.MakeIndex(input, typeof(List<bool>).GetProperty("Item"), new[] { Expression.Constant(count) });
    results.Add(Expression.IsTrue(indexAccess));
    count++;
}

Kết hợp điều kiện:

Expression condition = results[0];
for (int i = 1; i < results.Count; i++)
{
    if (logicRequests[i].Type == LogicType.And)
        condition = Expression.AndAlso(condition, results[i]);
    else if (logicRequests[i].Type == LogicType.Or)
        condition = Expression.Or(condition, results[i]);
}

Tạo biểu thức if-else:

var trueExpression = Expression.Constant(true);
var falseExpression = Expression.Constant(false);
var ifElseExpression = Expression.Condition(condition, trueExpression, falseExpression);

Biên dịch và thực thi biểu thức:

var lambda = Expression.Lambda<Func<List<bool>, bool>>(ifElseExpression, inputs).Compile();
var result = lambda(boolList);

Ưu tiên toán tử And hơn Or

Trong ví dụ trước, các toán tử AndOr được kết hợp theo thứ tự tuần tự. Nhưng trong nhiều tình huống, bạn muốn ưu tiên các điều kiện And. Có thể nhóm chúng lại trước khi áp dụng Or.

Đoạn mã dưới đây minh họa cách nhóm các điều kiện And:

Expression condition = results[0];
var andConditions = new List<Expression>();

for (int i = 1; i < results.Count; i++)
{
    if (logicRequests[i].Type == LogicType.And)
    {
        andConditions.Add(results[i]);
    }
    else if (logicRequests[i].Type == LogicType.Or)
    {
        if (andConditions.Any())
        {
            var andGroup = andConditions[0];
            for (int j = 1; j < andConditions.Count; j++)
                andGroup = Expression.AndAlso(andGroup, andConditions[j]);

            condition = Expression.AndAlso(condition, andGroup);
            andConditions.Clear();
        }
        condition = Expression.OrElse(condition, results[i]);
    }
}

// Xử lý các điều kiện And còn lại
if (andConditions.Any())
{
    var andGroup = andConditions[0];
    for (int j = 1; j < andConditions.Count; j++)
        andGroup = Expression.AndAlso(andGroup, andConditions[j]);

    condition = Expression.AndAlso(condition, andGroup);
}

Bằng cách này, bạn đảm bảo tất cả điều kiện And được xử lý theo nhóm trước khi dùng đến Or, mang lại kết quả logic chính xác hơn.

Expression Trees là công cụ mạnh trong C# giúp bạn tạo ra mã động và thực thi chúng trong runtime. Chúng cực kỳ hữu dụng trong các tình huống cần linh hoạt như tạo truy vấn động, thao tác dữ liệu, tối ưu mã hoặc chuyển đổi biểu thức. Tuy nhiên, vì cấu trúc khá phức tạp nên chúng đòi hỏi sự cẩn trọng và hiểu sâu để sử dụng hiệu quả.

Việc xây dựng biểu thức logic động với Expression Trees giúp mã linh hoạt, dễ bảo trì và dễ mở rộng, đặc biệt trong các ứng dụng yêu cầu xử lý điều kiện phức tạp theo thời gian thực.

#ntechdevelopers