您已在上一節(jié)中了解了表達(dá)式。現(xiàn)在,讓我們?cè)谶@里了解表達(dá)式樹(shù)。
顧名思義,表達(dá)式樹(shù)不過(guò)是按樹(shù)狀數(shù)據(jù)結(jié)構(gòu)排列的表達(dá)式。表達(dá)式樹(shù)中的每個(gè)節(jié)點(diǎn)都是一個(gè)表達(dá)式。例如,表達(dá)式樹(shù)可用于表示數(shù)學(xué)公式x <y,其中x,<和y將表示為表達(dá)式,并排列在樹(shù)狀結(jié)構(gòu)中。
表達(dá)式樹(shù)是lambda表達(dá)式的內(nèi)存表示形式。它保存查詢的實(shí)際元素,而不是查詢的結(jié)果。
表達(dá)式樹(shù)使lambda表達(dá)式的結(jié)構(gòu)透明和顯式。您可以與表達(dá)式樹(shù)中的數(shù)據(jù)進(jìn)行交互,就像與其他任何數(shù)據(jù)結(jié)構(gòu)一樣。
例如,看以下isTeenAgerExpr表達(dá)式:
Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;
編譯器會(huì)將上面的表達(dá)式轉(zhuǎn)換為以下表達(dá)式樹(shù):
示例:C#中的表達(dá)式樹(shù)
Expression.Lambda<Func<Student, bool>>( Expression.AndAlso( Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))), Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))), new[] { pe });
您也可以手動(dòng)構(gòu)建表達(dá)式樹(shù)。讓我們看看如何為以下簡(jiǎn)單的lambda表達(dá)式構(gòu)建表達(dá)式樹(shù):
示例:C#中的Func委托:
Func<Student, bool> isAdult = s => s.age >= 18;
此Func類(lèi)型委托將被視為以下方法:
C#:
public bool function(Student s) { return s.Age > 18; }
要?jiǎng)?chuàng)建表達(dá)式樹(shù),首先,創(chuàng)建一個(gè)參數(shù)表達(dá)式,其中Student是參數(shù)的類(lèi)型,'s'是參數(shù)的名稱(chēng),如下所示:
步驟1:在C#中創(chuàng)建參數(shù)表達(dá)式
ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
現(xiàn)在,使用Expression.Property()創(chuàng)建s.Age表達(dá)式,其中s是參數(shù),Age是Student的屬性名稱(chēng)。(Expression是一個(gè)抽象類(lèi),其中包含用于手動(dòng)創(chuàng)建表達(dá)式樹(shù)的靜態(tài)幫助器方法。)
步驟2:在C#中創(chuàng)建屬性表達(dá)式
MemberExpression me = Expression.Property(pe, "Age");
現(xiàn)在,為18創(chuàng)建一個(gè)常量表達(dá)式:
步驟3:在C#中創(chuàng)建常量表達(dá)式
ConstantExpression constant = Expression.Constant(18, typeof(int));
到目前為止,我們已經(jīng)為s.Age(成員表達(dá)式)和18(常量表達(dá)式)構(gòu)建了表達(dá)式樹(shù)?,F(xiàn)在,我們需要檢查成員表達(dá)式是否大于常量表達(dá)式。為此,請(qǐng)使用Expression.GreaterThanOrEqual() 方法,并將成員表達(dá)式和常量表達(dá)式作為參數(shù)傳遞::
步驟4:在C#中創(chuàng)建二進(jìn)制表達(dá)式
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
因此,我們?yōu)閘ambda表達(dá)式主體 s.Age> = 18 構(gòu)建了一個(gè)表達(dá)式樹(shù)。我們現(xiàn)在需要將參數(shù)表達(dá)式和主體表達(dá)式連接起來(lái)。使用Expression.Lambda
步驟5:在C#中創(chuàng)建Lambda表達(dá)式
var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
這樣,您可以為帶有l(wèi)ambda表達(dá)式的簡(jiǎn)單Func委托構(gòu)建表達(dá)式樹(shù)。
示例:C#中的表達(dá)式樹(shù)
ParameterExpression pe = Expression.Parameter(typeof(Student), "s"); MemberExpression me = Expression.Property(pe, "Age"); ConstantExpression constant = Expression.Constant(18, typeof(int)); BinaryExpression body = Expression.GreaterThanOrEqual(me, constant); var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe }); Console.WriteLine("表達(dá)式樹(shù): {0}", ExpressionTree); Console.WriteLine("表達(dá)式樹(shù)體: {0}", ExpressionTree.Body); Console.WriteLine("表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): {0}", ExpressionTree.Parameters.Count); Console.WriteLine("表達(dá)式樹(shù)中的參數(shù): {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s") Dim mexp As MemberExpression = Expression.Property(pe, "Age") Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer)) Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant) Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() {pe}) Console.WriteLine("表達(dá)式樹(shù): {0}", ExpressionTree) Console.WriteLine("表達(dá)式樹(shù)體: {0}", ExpressionTree.Body) Console.WriteLine("表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): {0}", ExpressionTree.Parameters.Count) Console.WriteLine("表達(dá)式樹(shù)中的參數(shù): {0}", ExpressionTree.Parameters(0))
表達(dá)式樹(shù): s => (s.Age >= 18) 表達(dá)式樹(shù)體: (s.Age >= 18) 表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): 1 表達(dá)式樹(shù)中的參數(shù): s
下圖說(shuō)明了創(chuàng)建表達(dá)式樹(shù)的整個(gè)過(guò)程:
在上一節(jié)中,我們已經(jīng)看到分配給lambda表達(dá)式Func<T>編譯為可執(zhí)行代碼,分配給lambda表達(dá)式Expression<TDelegate>類(lèi)型編譯為Expression樹(shù)。
可執(zhí)行代碼在同一個(gè)應(yīng)用程序域中執(zhí)行,以處理內(nèi)存中的集合??擅杜e的靜態(tài)類(lèi)包含用于實(shí)現(xiàn)IEnumerable <T>接口的內(nèi)存中集合的擴(kuò)展方法,例如List <T>,Dictionary <T>等。Enumerable類(lèi)中的擴(kuò)展方法接受Func類(lèi)型委托的謂詞參數(shù)。例如,Where擴(kuò)展方法接受Func <TSource,bool>謂詞。然后,將其編譯為IL(中間語(yǔ)言)以處理同一AppDomain中的內(nèi)存中集合。
下圖顯示了Enumerable類(lèi)中的where擴(kuò)展方法包括Func委托作為參數(shù)的情況:
Func委托是原始的可執(zhí)行代碼,因此,如果調(diào)試代碼,則會(huì)發(fā)現(xiàn)Func委托將表示為不透明代碼。您無(wú)法看到其參數(shù),返回類(lèi)型和主體:
Func委托用于內(nèi)存中的集合,因?yàn)樗鼘⒃谕粋€(gè)AppDomain中進(jìn)行處理,但是諸如LINQ-to-SQL,EntityFramework或其他提供LINQ功能的第三方產(chǎn)品的遠(yuǎn)程LINQ查詢提供者呢?他們將如何解析已編譯為原始可執(zhí)行代碼的lambda表達(dá)式,以了解參數(shù),lambda表達(dá)式的返回類(lèi)型以及構(gòu)建運(yùn)行時(shí)查詢以進(jìn)一步處理?答案是表達(dá)樹(shù)。
Expression <TDelegate>被編譯成稱(chēng)為表達(dá)式樹(shù)的數(shù)據(jù)結(jié)構(gòu)。
如果調(diào)試代碼,則表達(dá)式代表將如下所示:
現(xiàn)在您可以看到普通委托和表達(dá)式之間的區(qū)別。表達(dá)式樹(shù)是透明的。您可以從表達(dá)式中檢索參數(shù),返回類(lèi)型和主體表達(dá)式信息,如下所示:
Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20; Console.WriteLine("Expression: {0}", isTeenAgerExpr ); Console.WriteLine("表達(dá)式類(lèi)型: {0}", isTeenAgerExpr.NodeType); var parameters = isTeenAgerExpr.Parameters; foreach (var param in parameters) { Console.WriteLine("參數(shù)名稱(chēng): {0}", param.Name); Console.WriteLine("參數(shù)類(lèi)型: {0}", param.Type.Name ); } var bodyExpr = isTeenAgerExpr.Body as BinaryExpression; Console.WriteLine("表達(dá)式主體左側(cè): {0}", bodyExpr.Left); Console.WriteLine("二進(jìn)制表達(dá)式類(lèi)型: {0}", bodyExpr.NodeType); Console.WriteLine("表達(dá)式主體右側(cè): {0}", bodyExpr.Right); Console.WriteLine("返回類(lèi)型: {0}", isTeenAgerExpr.ReturnType);
Expression: s => ((s.Age > 12) AndAlso (s.Age < 20)) 表達(dá)式類(lèi)型: Lambda 參數(shù)名稱(chēng): s 參數(shù)類(lèi)型: Student 表達(dá)式主體左側(cè): (s.Age > 12) 二進(jìn)制表達(dá)式類(lèi)型: AndAlso 表達(dá)式主體右側(cè): (s.Age < 20) 返回類(lèi)型: System.Boolean
不在同一應(yīng)用程序域中執(zhí)行針對(duì)LINQ-to-SQL或Entity Framework的LINQ查詢。例如,對(duì)于Entity Framework的以下LINQ查詢永遠(yuǎn)不會(huì)在程序內(nèi)部實(shí)際執(zhí)行:
var query = from s in dbContext.Students where s.Age >= 18 select s;
首先將其轉(zhuǎn)換為SQL語(yǔ)句,然后在數(shù)據(jù)庫(kù)服務(wù)器上執(zhí)行。
在查詢表達(dá)式中找到的代碼必須轉(zhuǎn)換為SQL查詢,該查詢可以作為字符串發(fā)送到另一個(gè)進(jìn)程。對(duì)于LINQ-to-SQL或Entity Framework,該過(guò)程恰好是SQL Server數(shù)據(jù)庫(kù)。將數(shù)據(jù)結(jié)構(gòu)(如表達(dá)式樹(shù))轉(zhuǎn)換為SQL顯然比將原始IL或可執(zhí)行代碼轉(zhuǎn)換為SQL容易得多,因?yàn)檎缒吹降?,從表達(dá)式中檢索信息很容易。
創(chuàng)建表達(dá)式樹(shù)的目的是將諸如查詢表達(dá)式之類(lèi)的代碼轉(zhuǎn)換為可以傳遞給其他進(jìn)程并在此處執(zhí)行的字符串。
可查詢的靜態(tài)類(lèi)包括接受Expression類(lèi)型的謂詞參數(shù)的擴(kuò)展方法。將該謂詞表達(dá)式轉(zhuǎn)換為表達(dá)式樹(shù),然后將其作為數(shù)據(jù)結(jié)構(gòu)傳遞到遠(yuǎn)程LINQ提供程序,以便提供程序可以從表達(dá)式樹(shù)構(gòu)建適當(dāng)?shù)牟樵儾?zhí)行查詢。