什么是表達式樹,它與表達式、委托有什么區別?(1)


序言

  首先,需要普及下基礎知識:

  Expression我們稱之為:表達式樹,

  而Func<>或者Action 稱之為:匿名委托,Func與Action的區別是Func帶返回值(至少一個參數),Action不帶返回值(可以沒有任何參數)。

  以上的關鍵詞是在.net 3.5之后出現的,配合Linq中Lambda使用。

  當然Expression還可以動態的進行構造它,而不使用Lambda表達式來定義。

  補充:在CSDN上找到一篇文章講的是利用CodeDOM來生成,覺得有意思,分享下:地址

使用場景
  1. 你需要優雅的表達式
  2. 你可能需要將某種帶邏輯性的代碼轉成其它表現形式,比如(轉成string或數據庫的SQL腳本)。這會比你拼接string,顯的更優雅。
  3. 你可能需要制定一個動態的規則,讓客戶端調用。
什么是表達式樹

  它是一種數據結構體,用於存儲需要計算、運算的一種結構。這種結構可以只是”存儲“,而不進行運算。

  就好像我們寫了一個方法函數,而不調用它一樣。但是這種結構,我們是可以在程序在運行時,進去動態改變它的。而我們的Func一旦定義好編譯后是無法更改的。

  通常表達式樹是配合委托一起的,比如:Expression<Func<int,int>>。

雖然博主一直想以簡單明了的說明去描述它,但感覺越描越黑。。。。

   僅僅是拿Expression來描述它的概念覺得還是不易讓大家通俗易懂,所以我還是把它跟Func<>一起描述吧:

  首先Expression<Func<>>是可以轉成Func的。反過來則不行。我們可以理解為Func<>經過定義后,就無法改變它了。而表達式樹(Expression<Func<>>則是可以進行變更的。

  其次Expression<Func<>>如開頭描述,僅僅只是一種數據結構,或者說載體。它本身並沒有運算、計算的能力。如果需要這些”能力“,則必須轉為Func<>才行。

  或者我們可以這樣認為:Expression<Func<>>是變量,而Func<>是”方法函數“。

表達式樹與匿名委托使用場合

  兩者都可以通過Lambda語法進行定義,比如:

1 Expression<Func<UserVO, object>> exp = o => o.ID > 0 && o.UserName == "farseer.net";
2 Func<UserVO, object> fun = o => o.ID > 0 && o.UserName == "farseer.net";

  但是,Func一旦定義是無法在運行時改變它的,當然我這里說的改變是動態構造,而不是重新定義(賦值)。而表達式樹是可以的。

  比如上面的exp變量改變成:

Expression<Func<UserVO, object>> exp = o => o.ID != 1 && (o.UserName == "steden" || o.UserName == "farseer.net")

   並且,你無法在運行時知道Func的內部是什么,或者說它在做什么運算。Func的目地只是提供.net運算時的計算。

  精髓:在你需要把這種表達式,轉換成另外一種形式時,必須使用Expression。

  也可以狹義的理解為,好比Func是閉源的,你拿到別人的dll,無法改變它,只能調用它,而Expression好比你拿到了開源的代碼,你隨時可以更改它內部的結構。

  比如:在我的Farseer.Net中,需要開發者通過傳入上面的exp變量時,Farseer.Net能把它轉換成SQL:

1 select * from UserVO where ID > 0 and UserName = 'farseer.net'

  因為Expression是可以在運行時,分析它的數據結構。而Func是不可以的。(這里可以理解為Func是被”編譯“的。)

  因此,在你需要把.net的某些運算,轉換成其它表達形式,傳輸到其它進程、服務器、文件、數據庫時,必須使用表達式樹。因為它是允許通過代碼進行動態解析的。

Linq To xx 的使用

  解析完了上面所說的,回過頭來我們在想想Linq To Sql 跟Linq To Object時,里面的 Where方法的形參是如何定義的:

Linq To Object:

1 public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

Linq To Sql:

1 public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

   所以,現在我們知道為什么IQueryable.Where 用的是:Expression<Func<TSource, bool>> predicate參數了。(因為需要轉換成數據庫所能識別的SQL。)

  而如果我們是本地變量(Linq To Object)時,是這樣操作的:

1 var lst = new List<int>();
2 lst.Where(o => o > 0);

  注意lst.Where傳入的是Func。而不是Expression,因為這里的計算直接用.net framework來完成。而不需要轉換成其它語言(比如T-SQL)。

尾聲

  因為本系列主要講的是表達式樹,而不是委托,但大家容易把表達式樹與委托搞混淆,或者不清楚他們的區別,我們先通過這一篇來描述他們是什么,以及在什么場景下使用到。

  當然,在我們定義好一個委托時,我們可以立即調用它:

1 Func<int, bool> fun = o => o > 0;
2 // result = true,因為傳入100是大於0的
3 bool result = fun(100);

  而如果是表達式樹,也可以像委托那樣調用,不過我們必須通過調用Compile()轉成對應的委托后才可:

1 Expression<Func<int, bool>> fun = o => o > 0;
2 // result = true,因為傳入100是大於0的
3 bool result = fun.Compile()(100);

  看看Compile的注釋:

 1   /// <summary>
 2   /// 以表達式目錄樹的形式將強類型 lambda 表達式表示為數據結構。此類不能被繼承。
 3   /// </summary>
 4   /// <typeparam name="TDelegate"><see cref="T:System.Linq.Expressions.Expression`1"/> 表示的委托的類型。</typeparam>
 5   public sealed class Expression<TDelegate> : LambdaExpression
 6   {
 7     /// <summary>
 8     /// 將表達式樹描述的 lambda 表達式編譯為可執行代碼,並生成表示該 lambda 表達式的委托。
 9     /// </summary>
10     /// 
11     /// <returns>
12     /// 一個 <paramref name="TDelegate"/> 類型的委托,它表示由 <see cref="T:System.Linq.Expressions.Expression`1"/> 描述的已編譯的 lambda 表達式。
13     /// </returns>
14     public new TDelegate Compile();
總結

  通過本篇,簡單了解了表達式樹與委托的區別,后面的篇幅中我們核心講表達式樹。

  其實真要完整的描述清楚表達式樹及其使用,單靠幾篇博文來描述是很難的。但本着分享自己的學習心得態度來跟大家分享。

  表達式樹對於開發者來說是非常重要的一項技術,如果沒有表達式樹的出現,在以前性能、計算過程的緩存只能靠Emit來實現。(表達式樹實質也是由Emity實現的。)

  所以推薦所有.net開發者都需要學習並精通它。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM