C#中的Lambda表示式和表示式樹
在C# 2.0中,通過方法組轉換和匿名方法,使委託的實現得到了極大的簡化。但是,匿名方法仍然有些臃腫,而且當程式碼中充滿了匿名方法的時候,可讀性可能就會受到影響。C# 3.0中出現的Lambda表示式在不犧牲可讀性的前提下,進一步簡化了委託。
LINQ的基本功能就是建立操作管道,以及這些操作需要的任何狀態。這些操作表示了各種關於資料的邏輯,例如資料篩選,資料排序等等。通常這些操作都是用委託來表示。Lambda表示式是對LINQ資料操作的一種符合語言習慣的表示方式。
Lambda表示式不僅可以用來建立委託例項,C#編譯器也能夠將他們轉換成表示式樹。
下面我們就先看看Lambda表示式。
作為委託的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中,委託通常被視為資料管道的一部分,接受輸入並返回結果,或者判斷某項是否符合當前的篩選 器等等。
Lambda表示式本質
通過ILSpy檢視上面的例子,可以發現Lambda表示式就是匿名方法,是編譯器幫我們進行了轉換工作,使我們可以直接使用Lambda表示式來進一步簡化建立委託例項的程式碼。
在List<T>中使用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類包括兩個重要屬性:
- Type屬性代表求值表示式的.NET型別,可以把它視為一個返回型別
- NodeType屬性返回所代表的表示式的型別
下面看一個構建表示式樹的簡單例子:
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。
對於上面的例子,可以用下圖描述生成的表示式樹,值得注意的是,”葉子”表示式在程式碼中是最先建立的,,表示式是自下而上構建的。表示式是不易變的,所有可以快取和重用表示式。
將表示式編譯成委託
LambdaExpression是從Expression派生的型別之一。泛型型別Expression<TDelegate>又是從LambdaExpress派生的。
Expression和Expression<TDelegate>的區別在於,泛型類以靜態型別的方式標誌了它是什麼種類的表示式,也就是說,它確定了返回型別和引數。例如上面的加法例子,返回值是一個int型別,沒有引數,所以我們可以使用簽名Func<int>與之匹配,所以可以用Expression<Func<int>>以靜態型別的方式來表示該表示式。
這樣做的目的在於,LambdaExpression有一個Compile方法,該方法能建立一個恰當型別的委託。 Expression<TDelegate>也有一個同名方法,該方法可以返回TDelegate型別的委託。獲得了委託之後,我們就可以使 用普通委託例項呼叫的方式來執行這個表示式。
接著上面加法的例子,我們把上面的加法表示式樹轉換成委託,然後執行委託:
Func<int> addDelegate = Expression.Lambda<Func<int>>(add).Compile(); Console.WriteLine(addDelegate());
從這個例子中我們看到怎麼構建一個表示式樹,然後把這個物件樹編譯成真正的程式碼。在.NET 3.5中的表示式樹只能是單一的表示式,不能表示完整的類、方法。這在.NET 4.0中得到了一定的改進,表示式樹可以支援動態型別,我們可以建立塊,為表示式賦值等等。
將Lambda表示式轉換為表示式樹
Lambda表示式不僅可以建立委託例項,C# 3.0對於將Lambda表示式轉換成表示式樹提供了內建的支援。我們可以通過編譯器把Lambda表示式轉換成一個表示式樹,並建立一個Expression<TDelegate>的一個例項。
下面的例子中我們將一個Lambda表示式轉換成一個表示式樹,並通過程式碼檢視錶達式樹的各個部分:
static void Main(string[] args) { //將Lambda表示式轉換為型別Expression<T>的表示式樹 //expression不是可執行程式碼 Expression<Func<int, int, int>> expression = (a, b) => a + b; Console.WriteLine(expression); //獲取Lambda表示式的主體 BinaryExpression body = (BinaryExpression)expression.Body; Console.WriteLine(expression.Body); //獲取Lambda表示式的引數 Console.WriteLine(" param1: {0}, param2: {1}", expression.Parameters[0], expression.Parameters[1]); ParameterExpression left = (ParameterExpression)body.Left; ParameterExpression right = (ParameterExpression)body.Right; Console.WriteLine(" left body of expression: {0}{4} NodeType: {1}{4} right body of expression: {2}{4} Type: {3}{4}", left.Name, body.NodeType, right.Name, body.Type, Environment.NewLine); //將表示式樹轉換成委託並執行 Func<int, int, int> addDelegate = expression.Compile(); Console.WriteLine(addDelegate(10, 16)); Console.Read(); }
程式碼的輸出為:
表示式樹的用途
前面看到,通過Expression的派生類中的各種節點型別,我們可以構建表示式樹;然後可以把表示式樹轉換成相應的委託型別例項,最後執行委託例項的程式碼。但是,我們不會繞這麼大的彎子來執行委託例項的程式碼。
表示式樹主要在LINQ to SQL中使用,我們需要將LINQ to SQL查詢表示式(返回IQueryable型別)轉換成表示式樹。之所以需要轉換是因為LINQ to SQL查詢表示式不是在C#程式碼中執行的,LINQ to SQL查詢表示式被轉換成SQL,通過網路傳送,最後在資料庫伺服器上執行。
這裡只做個簡單的介紹,後續會介紹LINQ to SQL相關的內容。
編譯器對Lambda表示式的處理
前面我們瞭解到,Lambda可以用來建立委託例項,也可以用來生成表示式樹,這些都是編譯器幫我們完成的。
編譯器如何決定生成可執行的IL還是一個表示式樹:
- 當Lambda表示式賦予一個委託型別的變數時,編譯器生成與匿名方法同樣的IL(可執行的委託例項)
- 當Lambda表示式賦予一個Expression型別的變數時,編譯器就將它轉換成一個表示式樹
下圖展示了LINQ to Object和LINQ to SQL中Lambda表示式的不同處理方式:
總結
本文中介紹了Lambda表示式,在匿名方法的基礎上進一步簡化了委託例項的建立,編寫更加簡潔、易讀的程式碼。匿名函式不等於匿名方法,匿名函式包含了匿名方法和lambda表示式這兩種概念
Lambda不僅可以建立委託例項,還可以由編譯器轉換成表示式樹,使程式碼可以在程式之外執行(參考LINQ to SQL)。
相關文章
- C# Lambda表示式詳解,及Lambda表示式樹的建立C#
- C#中的表示式樹C#
- C#中的委託,匿名方法和Lambda表示式C#
- C#特性-匿名方法和Lambda表示式C#
- Java 中的 Lambda 表示式Java
- C# Lambda表示式的前世今生C#
- lambda 表示式
- lambda表示式
- C# 委託,事件和Lambda表示式 (轉)C#事件
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- C# Lambda表示式和linq表示式 之 匿名物件查詢接收C#物件
- Java中Lambda表示式的使用Java
- Java 8 中的 lambda 表示式Java
- c# 表示式樹(一)C#
- 【c#表示式樹】最完善的表示式樹Expression.Dynamic的玩法C#Express
- kotlin 函式和 Lambda 表示式Kotlin函式
- 5.函式和lambda表示式函式
- Java的Lambda表示式Java
- cpp的lambda表示式
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- 八,Lambda表示式
- Java Lambda表示式Java
- .NET 中的表示式樹
- Java8中的Lambda表示式Java
- Python中lambda表示式的用法Python
- Java中Lambda表示式的應用Java
- Java中lambda表示式詳解Java
- Lambda 表示式的應用
- 淺談lambda表示式
- Lambda表示式詳解
- Java之lambda表示式Java
- kotlin lambda表示式Kotlin
- Python - lambda 表示式Python
- Lambda表示式總結
- Java 8 Lambda 表示式Java
- C++Lambda表示式C++