在C# 2.0中,通過方法組轉換和匿名方法,使委托的實現得到了極大的簡化。但是,匿名方法仍然有些臃腫,而且當代碼中充滿了匿名方法的時候,可讀性可能就會受到影響。C# 3.0中出現的Lambda表達式在不犧牲可讀性的前提下,進一步簡化了委托。
LINQ的基本功能就是創建操作管道,以及這些操作需要的任何狀態。這些操作表示了各種關於數據的邏輯,例如數據篩選,數據排序等等。通常這些操作都是用委托來表示。Lambda表達式是對LINQ數據操作的一種符合語言習慣的表示方式。
Lambda表達式不僅可以用來創建委托實例,C#編譯器也能夠將他們轉換成表達式樹。
下面我們就先看看Lambda表達式。
Lambda表達式可以看作是C# 2.0的匿名方法的進一步演變,所以匿名方法能做的幾乎一切事情都可以用Lambda表達式來完成(注意,匿名方法可以忽略參數,Lambda表達式不具備這個特性)。
跟匿名方法類似,Lambda表達式有特殊的轉換規則:表達式的類型本身並非委托類型,但它可以通過隱式或顯式的發那個是轉換為一個委托實例。匿名函數這個術語同時涵蓋了匿名方法和Lambda表達式。
下面看看使用Lambda表達式獲得字符串長度的例子,通過Lambda將得到更見簡潔、易讀的代碼:
static void Main(string[] args) { //使用C# 2.0中的匿名方法獲取字符串長度 Func<string, int> strLength = delegate(string str) { return str.Length; }; Console.WriteLine(strLength("Hello World!")); //使用Lambda表達式 //(顯式類型參數列表)=> {語句},lambda表達式最冗長版本 strLength = (string str) => { return str.Length; }; Console.WriteLine(strLength("Hello World!")); //單一表達式作為主體 //(顯式類型參數列表)=> 表達式 strLength = (string str) => str.Length; Console.WriteLine(strLength("Hello World!")); //隱式類型的參數列表 //(隱式類型參數列表)=> 表達式 strLength = (str) => str.Length; Console.WriteLine(strLength("Hello World!")); //單一參數的快捷語法 //參數名 => 表達式 strLength = str => str.Length; Console.WriteLine(strLength("Hello World!")); }
"=>"是C# 3.0新增的,告訴編譯器我們正在使用Lambda表達式。"=>"可以讀作"goes to",所以例子中的Lambda表達式可以讀作"str goes to str.Length"。從例子中還可以看到,根據Lambda使用的特殊情況,我們可以進一步簡化Lambda表達式。
Lambda表達式大多數時候都是和一個返回非void的委托類型配合使用(例如Func<TResult>)。在C# 1.0中,委托一般用於事件,很少會返回什麼結果。在LINQ中,委托通常被視為數據管道的一部分,接受輸入並返回結果,或者判斷某項是否符合當前的篩選器等等。
通過ILSpy查看上面的例子,可以發現Lambda表達式就是匿名方法,是編譯器幫我們進行了轉換工作,使我們可以直接使用Lambda表達式來進一步簡化創建委托實例的代碼。
前面簡單的介紹了什麼是Lambda表達式,下面通過一個例子進一步了解Lambda表達式。
在前面的文章中,我們也提到了一下List<T>的方法,例如FindAll方法,參數是Predicate<T>類型的委托,返回結果是一個篩選後的新列表;Foreach方法獲取一個Action<T>類型的委托,然後對每個元素設置行為。下面就看看在List<T>中使用Lambda表達式:
public class Book { public string Name { get; set; } public int Year { get; set; } } class Program { static void Main(string[] args) { var books = new List<Book> { new Book{Name="C# learning guide",Year=2005}, new Book{Name="C# step by step",Year=2005}, new Book{Name="Java learning guide",Year=2004}, new Book{Name="Java step by step",Year=2004}, new Book{Name="Python learning guide",Year=2003}, new Book{Name="C# in depth",Year=2012}, new Book{Name="Java in depth",Year=2014}, new Book{Name="Python in depth",Year=2013}, }; //創建一個委托實例來表示一個通用的操作 Action<Book> printer = book => Console.WriteLine("Name = {0}, Year = {1}", book.Name, book.Year); books.ForEach(printer); //使用Lambda表達式對List<T>進行篩選 books.FindAll(book => book.Year > 2010).ForEach(printer); books.FindAll(book => book.Name.Contains("C#")).ForEach(printer); //使用Lambda表達式對List<T>進行排序 books.Sort((book1, book2) => book1.Name.CompareTo(book2.Name)); books.ForEach(printer); Console.Read(); } }
從上面例子可以看到,當我們要經常使用一個操作的時候,我們最好創建一個委托實例,然後反復調用,而不是每次使用的時候都使用Lambda表達式(例如例子中的printer委托實例)。
相比C# 1.0中的委托或者C# 2.0的匿名函數,結合Lambda表達式,對List<T>中的數據操作變得簡單,易讀。
表達式樹也稱表達式目錄樹,將代碼以一種抽象的方式表示成一個對象樹,樹中每個節點本身都是一個表達式。表達式樹不是可執行代碼,它是一種數據結構。
下面我們看看怎麼通過C#代碼建立一個表達式樹。
System.Linq.Expressions命名空間中包含了代表表達式的各個類,所有類都從Expression派生,我們可以通過這些類中的靜態��法來創建表達式類的實例。Expression類包括兩個重要屬性:
下面看一個構建表達式樹的簡單例子:
Expression numA = Expression.Constant(6); Console.WriteLine("NodeType: {0}, Type: {1}", numA.NodeType, numA.Type); Expression numB = Expression.Constant(3); Console.WriteLine("NodeType: {0}, Type: {1}", numB.NodeType, numB.Type); BinaryExpression add = Expression.Add(numA, numB); Console.WriteLine("NodeType: {0}, Type: {1}", add.NodeType, add.Type); Console.WriteLine(add); Console.Read();
代碼的輸出為:
通過例子可以看到,我們構建了一個(6+3)的表達式樹,並且查看了各個節點的Type和NodeType屬性。
Expression有很多派生類,有很多節點類型。例如,BinaryExpression就代表了具有兩個操作樹的任意操作。這正是NodeType屬性重要的地方,它能區分由相同的類表示的不同種類的表達式。其他的節點類型就不介紹了,有興趣可以參考MSDN。
對於上面的例子,可以用下圖描述生成的表達式樹,值得注意的是,"葉子"表達式在代碼中是最先創建的,,表達式是自下而上構建的。表達式是不易變的,所有可以緩存和重用表達式。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-02/114223p2.htm