C# 表達式樹 (Expression Trees) 詳解
表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,比如方法調用和 x < y 這樣的二元運算等。你可以對表達式樹中的代碼進行編輯和運算。這樣能夠動態修改可執行代碼、在不同數據庫中執行 LINQ 查詢以及創建動態查詢。表達式樹還能用于動態語言運行時 (DLR) 以提供動態語言和 .NET Framework 之間的互操作性。
Lambda 表達式創建表達式樹
若 lambda 表達式被分配給 Expression<TDelegate> 類型的變量,則編譯器可以發射代碼以創建表示該 lambda 表達式的表達式樹。C# 編譯器只能從表達式 lambda (或單行 lambda)生成表達式樹。
下列代碼示例使用關鍵字 Expression 創建表示 lambda 表達式:
using System.Linq.Expressions;
namespace AppExpressionTrees
{
internal class Program
{
static void Main(string[] args)
{
Expression<Action<int>> actionExpression = n => Console.WriteLine(n);
Expression<Func<int, bool>> funcExpression1 = (n) => n < 0;
Expression<Func<int, int, bool>> funcExpression2 = (n, m) => n - m == 0;
Console.WriteLine(actionExpression);
Console.WriteLine(funcExpression1);
Console.WriteLine(funcExpression2);
}
}
}
圖片
API 創建表達式樹
通過 API 創建表達式樹需要使用 Expression 類。下列代碼示例展示如何通過 API 創建表示 lambda 表達式 num => num == 0:
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// 通過 Expression 類創建表達式樹
// lambda:num => num == 0
ParameterExpression pExpression = Expression.Parameter(typeof(int)); // 參數:num
ConstantExpression cExpression = Expression.Constant(0); // 常量:0
BinaryExpression bExpression = Expression.MakeBinary(ExpressionType.Equal, pExpression, cExpression); // 表達式:num == 0
Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(bExpression, pExpression); // lambda 表達式:num => num == 0
Console.WriteLine(lambda);
}
}
圖片
另一個例子:創建一個簡單的加法表達式
using System.Linq.Expressions;
namespace AppExpressionTrees
{
internal class Program
{
static void Main(string[] args)
{
// 創建表達式樹:num1 + num2
ParameterExpression num1 = Expression.Parameter(typeof(int), "num1");
ParameterExpression num2 = Expression.Parameter(typeof(int), "num2");
BinaryExpression addExpression = Expression.Add(num1, num2);
Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(addExpression, num1, num2);
Console.WriteLine(lambda);
Console.WriteLine(lambda.Compile().Invoke(1, 2));
}
}
}
圖片
解析表達式樹
下列代碼示例展示如何分解表示 lambda 表達式 num => num == 0 的表達式樹:
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, bool>> funcExpression = num => num == 0;
// 開始解析
ParameterExpression pExpression = funcExpression.Parameters[0]; // lambda 表達式參數
BinaryExpression body = (BinaryExpression)funcExpression.Body; // lambda 表達式主體:num == 0
Console.WriteLine($"解析:{pExpression.Name} => {body.Left} {body.NodeType} {body.Right}");
}
}另一個例子:解析加法表達式
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> funcExpression = (num1, num2) => num1 + num2;
// 開始解析
ParameterExpression pExpression1 = funcExpression.Parameters[0]; // 第一個參數
ParameterExpression pExpression2 = funcExpression.Parameters[1]; // 第二個參數
BinaryExpression body = (BinaryExpression)funcExpression.Body; // lambda 表達式主體:num1 + num2
Console.WriteLine($"解析:{pExpression1.Name} + {pExpression2.Name} => {body.Left} {body.NodeType} {body.Right}");
}
}
圖片
表達式樹的永久性
表達式樹應具有永久性(類似字符串)。這意味著如果你想修改某個表達式樹,則必須復制該表達式樹然后替換其中的節點來創建一個新的表達式樹。你可以使用表達式樹訪問者遍歷現有表達式樹。第七節介紹了如何修改表達式樹。
編譯表達式樹
Expression<TDelegate> 類型提供了 Compile 方法以將表達式樹表示的代碼編譯成可執行委托。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// 創建表達式樹
Expression<Func<string, int>> funcExpression = msg => msg.Length;
// 表達式樹編譯成委托
var lambda = funcExpression.Compile();
// 調用委托
Console.WriteLine(lambda("Hello, World!"));
// 語法簡化
Console.WriteLine(funcExpression.Compile()("Hello, World!"));
}
}
圖片
另一個例子:編譯加法表達式
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// 創建表達式樹:num1 + num2
ParameterExpression num1 = Expression.Parameter(typeof(int), "num1");
ParameterExpression num2 = Expression.Parameter(typeof(int), "num2");
BinaryExpression addExpression = Expression.Add(num1, num2);
Expression<Func<int, int, int>> lambda = Expression.Lambda<Func<int, int, int>>(addExpression, num1, num2);
// 編譯表達式樹
var compiledLambda = lambda.Compile();
// 調用委托
Console.WriteLine(compiledLambda(3, 4)); // 輸出:7
}
}
圖片
執行表達式樹
執行表達式樹可能會返回一個值,也可能僅執行一個操作(例如調用方法)。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
constint n = 1;
constint m = 2;
// 待執行的表達式樹
BinaryExpression bExpression = Expression.Add(Expression.Constant(n), Expression.Constant(m));
// 創建 lambda 表達式
Expression<Func<int>> funcExpression = Expression.Lambda<Func<int>>(bExpression);
// 編譯 lambda 表達式
Func<int> func = funcExpression.Compile();
// 執行 lambda 表達式
Console.WriteLine($"{n} + {m} = {func()}");
}
}另一個例子:執行字符串長度表達式
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
// 創建表達式樹:msg => msg.Length
ParameterExpression msg = Expression.Parameter(typeof(string), "msg");
MemberExpression length = Expression.Property(msg, "Length");
Expression<Func<string, int>> lambda = Expression.Lambda<Func<string, int>>(length, msg);
// 編譯表達式樹
var compiledLambda = lambda.Compile();
// 執行 lambda 表達式
Console.WriteLine(compiledLambda("Hello, World!")); // 輸出:13
}
}
圖片
修改表達式樹
該類繼承 ExpressionVisitor 類,通過 Visit 方法間接調用 VisitBinary 方法將 != 替換成 ==?;惙椒嬙祛愃朴趥魅氲谋磉_式樹的節點,但這些節點將其子目錄樹替換為訪問器遞歸生成的表達式樹。
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, bool>> funcExpression = num => num == 0;
Console.WriteLine($"Source: {funcExpression}");
var visitor = new NotEqualExpressionVisitor();
var expression = visitor.Visit(funcExpression);
Console.WriteLine($"Modify: {expression}");
}
publicclass NotEqualExpressionVisitor : ExpressionVisitor
{
public Expression Visit(BinaryExpression node)
{
return VisitBinary(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
return node.NodeType == ExpressionType.Equal
? Expression.MakeBinary(ExpressionType.NotEqual, node.Left, node.Right) // 重新弄個表達式:用 != 代替 ==
: base.VisitBinary(node);
}
}
}
圖片
另一個例子:將加法修改為乘法
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, int, int>> funcExpression = (num1, num2) => num1 + num2;
Console.WriteLine($"Source: {funcExpression}");
var visitor = new MultiplyExpressionVisitor();
var expression = visitor.Visit(funcExpression);
Console.WriteLine($"Modify: {expression}");
}
publicclass MultiplyExpressionVisitor : ExpressionVisitor
{
public Expression Visit(BinaryExpression node)
{
return VisitBinary(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
return node.NodeType == ExpressionType.Add
? Expression.MakeBinary(ExpressionType.Multiply, node.Left, node.Right) // 重新弄個表達式:用 * 代替 +
: base.VisitBinary(node);
}
}
}總結
以上內容詳細介紹了 C# 中表達式樹的相關知識,并提供了具體的示例代碼,以幫助開發者更深入地理解這一重要概念。表達式樹是 C# 中一種強大的工具,它允許開發者以樹形結構表示代碼邏輯,從而可以在運行時對代碼進行分析、修改甚至動態生成。這種特性在許多場景下非常有用,例如構建動態查詢(如 LINQ to SQL)、實現自定義解析器或優化運行時性能。
希望這些內容能夠為你提供清晰的指導,幫助你更好地理解和使用表達式樹。無論你是初學者還是有一定經驗的開發者,都可以從中獲得啟發,并進一步探索表達式樹在各種復雜場景中的潛力。通過不斷實踐和嘗試,相信你會更加熟練地運用這一強大工具,提升代碼的靈活性和效率。




















