# C#表達式樹Expression怎么創建
## 一、表達式樹概述
表達式樹(Expression Tree)是.NET Framework 3.5引入的重要特性,它將代碼以樹狀數據結構的形式表示,每個節點都是一個表達式。這種數據結構使開發者能夠以編程方式分析、修改或動態生成代碼邏輯。
### 1.1 表達式樹的核心特點
- **運行時代碼生成**:可在程序運行時動態構建代碼邏輯
- **延遲執行**:表達式樹本身不立即執行,只有在編譯后才會運行
- **可解析性**:可以分解和檢查表達式樹的各個組成部分
- **LINQ基礎**:是LINQ to SQL等技術的實現基礎
### 1.2 表達式樹的主要用途
- 動態SQL生成(如Entity Framework)
- 動態方法生成
- 規則引擎實現
- AOP編程
- 反射的替代方案(性能更高)
## 二、表達式樹基礎類型
在System.Linq.Expressions命名空間下,包含構建表達式樹所需的核心類:
### 2.1 主要表達式類型
| 類型 | 描述 | 示例 |
|------|------|------|
| `ConstantExpression` | 常量表達式 | `5`, `"hello"` |
| `ParameterExpression` | 參數表達式 | `x` in `x => x + 1` |
| `BinaryExpression` | 二元運算表達式 | `a + b`, `x > y` |
| `MethodCallExpression` | 方法調用表達式 | `Console.WriteLine()` |
| `MemberExpression` | 成員訪問表達式 | `person.Name` |
| `LambdaExpression` | Lambda表達式 | `x => x * 2` |
| `UnaryExpression` | 一元運算表達式 | `!flag`, `(int)num` |
### 2.2 表達式樹構建流程
1. 創建參數表達式(如果需要)
2. 組合各種子表達式
3. 構建Lambda表達式
4. 編譯為委托(可選)
5. 執行(可選)
## 三、創建表達式樹的四種方式
### 3.1 通過Lambda表達式自動轉換
最簡單的方式是讓編譯器自動將Lambda轉換為表達式樹:
```csharp
// 自動轉換
Expression<Func<int, int, int>> addExpr = (a, b) => a + b;
// 查看表達式樹結構
Console.WriteLine(addExpr); // 輸出:(a, b) => (a + b)
限制: - 只能轉換單行Lambda - 不能包含語句塊 - 不能包含賦值操作等
完整手動構建示例:
// 創建參數
ParameterExpression paramA = Expression.Parameter(typeof(int), "a");
ParameterExpression paramB = Expression.Parameter(typeof(int), "b");
// 創建加法表達式
BinaryExpression add = Expression.Add(paramA, paramB);
// 創建Lambda表達式
Expression<Func<int, int, int>> lambda =
Expression.Lambda<Func<int, int, int>>(add, paramA, paramB);
// 編譯為委托并執行
Func<int, int, int> compiled = lambda.Compile();
int result = compiled(3, 5); // 返回8
Expression<Func<int, int>> square = x => x * x;
Expression<Func<int, int, int>> add = (a, b) => a + b;
// 組合:square(add(a,b))
var combined = Expression.Invoke(square,
Expression.Invoke(add, square.Parameters[0], Expression.Constant(2)));
Expression<Func<int, int>> finalExpr =
Expression.Lambda<Func<int, int>>(combined, square.Parameters[0]);
生成 person => person.Age > 18 && person.Name.Contains("張")
:
// 定義Person類
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
// 構建表達式
var personParam = Expression.Parameter(typeof(Person), "person");
// person.Age > 18
var ageProp = Expression.Property(personParam, "Age");
var ageCompare = Expression.GreaterThan(ageProp, Expression.Constant(18));
// person.Name.Contains("張")
var nameProp = Expression.Property(personParam, "Name");
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var nameCheck = Expression.Call(nameProp, containsMethod, Expression.Constant("張"));
// 組合
var finalCondition = Expression.AndAlso(ageCompare, nameCheck);
var lambda = Expression.Lambda<Func<Person, bool>>(finalCondition, personParam);
// 使用示例
var func = lambda.Compile();
bool test = func(new Person { Name = "張三", Age = 20 }); // true
構建類似三元運算符的條件邏輯:
// x => x > 0 ? x * 2 : x + 1
ParameterExpression x = Expression.Parameter(typeof(int), "x");
// 條件判斷
BinaryExpression condition = Expression.GreaterThan(x, Expression.Constant(0));
// true分支
BinaryExpression trueExpr = Expression.Multiply(x, Expression.Constant(2));
// false分支
BinaryExpression falseExpr = Expression.Add(x, Expression.Constant(1));
// 條件表達式
ConditionalExpression conditional =
Expression.Condition(condition, trueExpr, falseExpr);
var lambda = Expression.Lambda<Func<int, int>>(conditional, x);
雖然表達式樹不支持直接的for/while
循環,但可以通過遞歸模擬:
// 遞歸實現階乘
ParameterExpression n = Expression.Parameter(typeof(int), "n");
ParameterExpression result = Expression.Variable(typeof(int), "result");
LabelTarget label = Expression.Label(typeof(int));
BlockExpression block = Expression.Block(
new[] { result },
Expression.Assign(result, Expression.Constant(1)),
Expression.Loop(
Expression.IfThenElse(
Expression.GreaterThan(n, Expression.Constant(1)),
Expression.Block(
Expression.MultiplyAssign(result, n),
Expression.PostDecrementAssign(n)),
Expression.Break(label, result)
),
label
)
);
var factorial = Expression.Lambda<Func<int, int>>(block, n);
// try { 10/x } catch { -1 }
ParameterExpression x = Expression.Parameter(typeof(int), "x");
ConstantExpression ten = Expression.Constant(10);
ConstantExpression minusOne = Expression.Constant(-1);
TryExpression tryCatchExpr = Expression.TryCatch(
Expression.Block(
Expression.Divide(ten, x)
),
Expression.Catch(
typeof(DivideByZeroException),
minusOne
)
);
var lambda = Expression.Lambda<Func<int, int>>(tryCatchExpr, x);
使用ExpressionTreeVisualizer工具或直接輸出:
Expression<Func<int, int, int>> expr = (a, b) => (a + b) * 2;
Console.WriteLine(expr.ToString());
// 輸出:
// (a, b) => ((a + b) * 2)
void AnalyzeExpression(Expression expr, int indent = 0)
{
string padding = new string(' ', indent * 2);
Console.WriteLine($"{padding}NodeType: {expr.NodeType}");
Console.WriteLine($"{padding}Type: {expr.Type}");
if (expr is BinaryExpression binary)
{
Console.WriteLine($"{padding}Left:");
AnalyzeExpression(binary.Left, indent + 1);
Console.WriteLine($"{padding}Right:");
AnalyzeExpression(binary.Right, indent + 1);
}
else if (expr is LambdaExpression lambda)
{
foreach (var param in lambda.Parameters)
{
Console.WriteLine($"{padding}Param: {param.Name}");
}
AnalyzeExpression(lambda.Body, indent + 1);
}
}
緩存編譯結果:重復使用已編譯的委托
private static readonly Func<Person, bool> cachedFilter =
lambda.Compile();
避免頻繁編譯:編譯操作比較耗時
使用Expression.Quote處理嵌套Lambda:
Expression<Func<IQueryable<User>, IOrderedQueryable<User>>> expr =
q => q.OrderBy(u => u.Name);
優先使用簡單表達式:復雜表達式會增加編譯時間
考慮使用FastExpressionCompiler等第三方庫提升性能
IQueryable<Employee> DynamicFilter(IQueryable<Employee> source,
string propertyName, object value)
{
var param = Expression.Parameter(typeof(Employee), "e");
var prop = Expression.Property(param, propertyName);
var constant = Expression.Constant(value);
var equal = Expression.Equal(prop, constant);
var lambda = Expression.Lambda<Func<Employee, bool>>(equal, param);
return source.Where(lambda);
}
Expression<Func<Source, Destination>> CreateMapper<Source, Destination>()
{
var sourceParam = Expression.Parameter(typeof(Source), "src");
var bindings = new List<MemberBinding>();
foreach (var destProp in typeof(Destination).GetProperties())
{
var srcProp = typeof(Source).GetProperty(destProp.Name);
if (srcProp != null)
{
var propExpr = Expression.Property(sourceParam, srcProp);
bindings.Add(Expression.Bind(destProp, propExpr));
}
}
return Expression.Lambda<Func<Source, Destination>>(
Expression.MemberInit(Expression.New(typeof(Destination)), bindings),
sourceParam);
}
Q1:表達式樹和委托有什么區別?
A1:委托是編譯好的可執行代碼,而表達式樹是可分析的數據結構。表達式樹可以轉換為委托,但反之不行。
Q2:為什么我的Lambda不能轉換為表達式樹?
A2:只有滿足以下條件的Lambda才能自動轉換: - 不包含語句體(只能是表達式) - 不包含賦值操作 - 不包含動態類型
Q3:如何調試表達式樹編譯錯誤?
A3:
1. 逐步檢查每個表達式節點
2. 使用Expression.ToString()
查看部分結構
3. 驗證所有使用的成員和方法是否存在
Q4:表達式樹有性能開銷嗎?
A4: - 構建表達式樹開銷很小 - 編譯為委托的開銷較大(需緩存結果) - 執行編譯后的委托與普通委托性能相當
表達式樹是C#中強大的元編程工具,通過本文我們學習了: 1. 四種創建表達式樹的方法 2. 高級構建技巧(條件、循環、異常) 3. 調試與分析方法 4. 實際應用場景 5. 性能優化策略
掌握表達式樹可以極大地提升動態代碼生成能力,為構建靈活的系統架構提供強大支持。建議從簡單示例開始,逐步嘗試更復雜的場景,最終將其應用到實際項目中。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。