寫在開頭,好奇從這裡開始(當時讓加查詢條件,結果竟然是一句話來發揮神奇作用): 1. 語法糖 Lambda 在我看來,=>總是一個無敵可愛的符號。嗯,包括C語言裡面的 -> 這個,它總像是在說“我指到這邊,你看..”。 找到了一張圖[1],可以很好地說明lambda表達式,語法糖上的變化。 下麵有一 ...
寫在開頭,好奇從這裡開始(當時讓加查詢條件,結果竟然是一句話來發揮神奇作用):
this.TestGrade = CriteriaHelper.NewObject<ITestCase, DtoTestCase>("測試等級", a => a.Grade);
1. 語法糖 Lambda
在我看來,=>總是一個無敵可愛的符號。嗯,包括C語言裡面的 -> 這個,它總像是在說“我指到這邊,你看..”。
找到了一張圖[1],可以很好地說明lambda表達式,語法糖上的變化。
// 匿名方法,方法如何寫應該是深入骨髓的哦: delegate(string text) { return text.Length; } // Lambda表達式,作用與上行相同: (string text) => { return text.Length; } // 還可以省略括弧,這看起來就更像goes to“看到這裡”的感覺了: (string text) => text.Length // 讓編譯器推斷參數類型: (text) => text.Length // 還可以省略括弧: text => text.Length
註:Lambda表達式在去掉花括弧時(即僅有一句時),其實是不帶;符號的,一般加上的;是賦值語句規則的。
下麵有一個例子,一個讓人覺得“怎麼會有傳方法這種這麼神奇操作”的例子:
class Film { public string Name { get; set; } public int Year { get; set; } } // Main函數: var films = new List<Film> { new Film { Name = "Jaws", Year = 1975}, new Film { Name = "Singing in the Rain", Year = 1952}, new Film { Name = "Some like it Hot", Year = 1959}, new Film { Name = "The Wizard of Oz", Year = 1939}, new Film { Name = "American Beauty", Year = 1999} }; Action<Film> print = film => Console.WriteLine("Name = {0}, Year = {1}", film.Name, film.Year); films.ForEach(print); // Console.WriteLine("\r"); films.FindAll(film => film.Year < 1955).ForEach(print); // Console.WriteLine("\r"); films.Sort((f1,f2) => f1.Name.CompareTo(f2.Name)); films.ForEach(print);
2. Lambda表達式與表達式樹(Expression tree)
在開啟本節內容前,先捋清一下:
圖2 [2]
C#編譯成公共中間語言(CIL或者IL,成果就是組件.dll文件),程式運行時再由實時編譯器(Just in Time,JIT)轉化為特定於CPU的語言。
2.1 表達式樹
表達式樹就是對象構成的樹,樹中每個節點本身都是一個表達式。Expression類主要包括兩個屬性:
Type屬性:表達式求值後的返回類型。
NodeType屬性:返回所代表的表達式的種類,也是節點類型。
表達式樹,有很多不同類型的節點。
例如可以這樣想象:一個數是一個節點,一個運算符也是一個節點,那麼一個簡單的2+3表達式,就是有3個節點的樹形關係。
Expression firstArg = Expression.Constant(2); Expression secondArg = Expression.Constant(3); Expression add = Expression.Add(firstArg, secondArg); Console.WriteLine(add); // 列印出來的是表達式 (2 + 3)
註意:“葉”表達式總是最先創建的。
註意:Expression.Constant或者是Expression.Add類型都是繼承於Expression的,這棵樹的所有節點,都是Expression類型。
add BinaryExpression NodeType = Add Type = System.Int32 |
|
firstArg ConstantExpression NodeType = Constant Type = System.Int32 |
secondArg ConstantExpression NodeType = Constant Type = System.Int32 |
圖3 上例代碼表達式樹樹形化表示
繼續上面的例子,既然列印出來的僅是表達式(2 + 3) ,那麼能不能列印運行結果呢?
可以想象一下,節點裡面不管是數值或是運算符,其實對於表達式樹來說,也就是多個節點或者少個節點的事。如果把節點當成沒有實際意義的“聖誕樹掛飾”(因為樹上的節點“不幹活”),那表達式樹到底有什麼意義?
那句話是這樣的,“沒有Lambda表達式,表達式樹幾乎沒有任何價值”。
我算是有些明白,“code as data”是什麼意思。沒有數據,代碼是惘然的。但是“葉子”有了,怎麼讓樹“活過來”?微軟提供的方式是:將表達式樹編譯成委托——既然有各種類型的節點,怎麼不能有方法類型的節點呢。
Expression leftArg = Expression.Constant(2); Expression rightArg = Expression.Constant(3); Expression add = Expression.Add(leftArg, rightArg); Func<int> compiled = Expression.Lambda<Func<int>>(add).Compile(); Console.WriteLine(compiled());
C#當中Lambda表達式支持表達式樹,不過還需要該節點使用Compile()方法(大概就是編譯成對應的Func<T>類型,此方法涉及到System.Reflection.MethodInfo,這節內容放到後面來談)。
Expression<Func<int>> return5 = () => 5; // () => 5是一個Lambda表達式 Func<int> compiled = return5.Compile(); // 把Lambda表達式轉換成表達式樹。
// (這種轉換有限制,此處不詳談。)
2.2 Lambda表達式與LINQ
LINQ(Language Integrated Query)是一種基於中間查詢能夠直接反饋到C#語言環境當中的技術。
LINQ提供器的中心思想在於:從一個熟悉的源語言(比如C#)生成一個表達式樹,將結果作為一個中間格式,再將其轉換成目標平臺上的本地語言(比如SQL)。
LINQ to Objects方式是:使用含有Lambda表達式的C#查詢代碼,再由轉化為相應的IL,在CLR中執行。
LINQ to SQL方式:編譯成使用表達式樹IL,在執行時,動態執行sql語句(IL已經處理好怎麼讓LINQ to SQL提供器處理到本地語言)。
……
除了LINQ to Objects和LINQ to SQL還有其他的數據查詢方式,此處不展開。
==================================================================================
下節預告:
Lambda表達式提供編譯時檢查的能力,它與表達式樹一起的話,會有神奇反應。
註釋:
[1] Lambda語法簡寫
[2] 圖源自 維基百科
[3] 此筆記主要參考 《深入理解C#》(第3版)Jon Skeet 著 姚琪琳 譯