動態創建 Lambda 表達

作爲 Delegate 的更深度進化,Lambda 讓我們的代碼顯得更加簡潔和優雅,但同時也面臨一個問題,就是如何依據外部條件動態構建一個 Lambda 表達式。或許你會奇怪這個需求是如何產生的…… 首先,Lambda 在 DLinq 中承擔了以往 T-SQL 的部分角色;其次,在數據庫設計中,我們往往需要依據外部未知的動態條件組合來查詢數據。而問題在於作爲一種靜態語言,我們顯然無法用動態語法或者拼接字符串的方法來創建一個Delegate/Lambda,那麼如何達到類似的目的呢?CodeDom?Emit?或許最佳的選擇是 System.Linq.Expressions.Expression。

首先我們看一個簡單 Lambda 表達式的構成。
i => i > 5

在這個表達式中,"i" 被稱爲 Parameter,"i > 5" 是 Body。我們可以對 Body 進行更進一步的分解,那麼 "i > 5" 分別包含參數(i)、操作符(>)以及一個常數(5)。所有這些通過特定順序的組合,從而構建一個完整的 Lambda 表達式。

我們通過一些例子,來學習如何動態構建這些表達式。

例子1
var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//var r = ints.Where(i => i > 5); // 要實現的表達式

// 創建參數 i
var parameter = Expression.Parameter(typeof(int), "i");

// 創建常量5
var constant = Expression.Constant(5);

// 創建比較表達式 i > 5
var bin = Expression.GreaterThan(parameter, constant);

// 獲取Lambda表達式
var lambda = Expression.Lambda<Func<int, bool>>(bin, parameter);

// 通過 Compile 方法獲取 Delegate
var _r = ints.Where(lambda.Compile());



在代碼中設置斷點,我們可以看到調試器中顯示的表達式信息。
 

.NET FX 3.5 中爲 Lambda 新增了一些委託類型。

(1) 用於處理無返回數據的 Action。

public delegate void Action()
public delegate void Action<T> (T arg)
public delegate void Action<T1, T2> (T1 arg1, T2 arg2)
public delegate void Action<T1, T2, T3> (T1 arg1, T2 arg2, T3 arg3)
public delegate void Action<T1, T2, T3, T4> (T1 arg1, T2 arg2, T3 arg3, T4 arg4)


(2) 用於處理帶返回數據的 Func。

public delegate TResult Func<TResult> ()
public delegate TResult Func<T, TResult> (T arg)
public delegate TResult Func<T1, T2, TResult> (T1 arg1, T2 arg2)
public delegate TResult Func<T1, T2, T3, TResult> (T1 arg1, T2 arg2, T3 arg3)
public delegate TResult Func<T1, T2, T3, T4, TResult> (T1 arg1, T2 arg2, T3 arg3, T4 arg4)


我們還可以進行更復雜的組合。

例子2

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// var r = ints.Where(i => i > 5 && i <= 7); // 要實現的表達式

// 創建參數 i
var parameter = Expression.Parameter(typeof(int), "i");

// 創建表達式 i > 5
var con1 = Expression.Constant(5);
var bin1 = Expression.GreaterThan(parameter, con1);

// 創建表達式 i <= 7
var con2 = Expression.Constant(7);
var bin2 = Expression.LessThanOrEqual(parameter, con2);

// 組合兩個表達式
var body = Expression.And(bin1, bin2);

// 獲取 Lambda 表達式
var lambda = Expression.Lambda<Func<int, bool>>(body, parameter);

var _r = ints.Where(lambda.Compile());


在例子2中,我們對複雜的表達式進行了分解,並使用 And 完成多個表達式的組裝,由此我們可以創建更加複雜的邏輯組合,比如例子3。

例子3

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// var r = ints.Where(i => (i > 5 && i <= 7) || (i == 3)); // 要實現的表達式

// 創建參數 i
var parameter = Expression.Parameter(typeof(int), "i");

// 創建表達式 i > 5
var con1 = Expression.Constant(5);
var bin1 = Expression.GreaterThan(parameter, con1);

// 創建表達式 i < 7
var con2 = Expression.Constant(7);
var bin2 = Expression.LessThanOrEqual(parameter, con2);

// 創建表達式 i == 3
var con3 = Expression.Constant(3);
var bin3 = Expression.Equal(parameter, con3);

// 組合 i > 5 && i <= 7
var body = Expression.And(bin1, bin2);

// 組合 ( i > 5 && i <= 7) OR (i == 3)
body = Expression.Or(body, bin3);

var lambda = Expression.Lambda<Func<int, bool>>(body, parameter);
var _r = ints.Where(lambda.Compile());


我們繼續看幾個常見的例子。

例子4

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//var r = ints.Select(i => i % 2 == 0 ? i : 0); // 要實現的表達式

// 創建參數 i
var parameter = Expression.Parameter(typeof(int), "i");

// 創建表達式 i % 2
var con1 = Expression.Constant(2);
var bin1 = Expression.Modulo(parameter, con1);

// 創建表達式 (i % 2) == 0
var con2 = Expression.Constant(0);
var bin2 = Expression.Equal(bin1, con2);

// 創建表達式 IIF(((i % 2) = 0), i, 0)
var bin3 = Expression.Condition(bin2, parameter, Expression.Constant(0));

var lambda = Expression.Lambda<Func<int, int>>(bin3, parameter);
var _r = ints.Select(lambda.Compile());


例子5

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Array.ForEach<int>(ints, i => Console.WriteLine(i)); // 要實現的表達式

// 創建參數i
var parameter = Expression.Parameter(typeof(int), "i");

// 獲取 Console.WriteLine MethodInfo
MethodInfo method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) });

// 創建表達式
var call = Expression.Call(method, parameter);

var lambda = Expression.Lambda<Action<int>>(call, parameter);
Array.ForEach<int>(ints, lambda.Compile());


是該花點時間去好好研究一下 System.Linq.Expressions Namespace 了…… [lol]

 

發佈了3 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章