Lambda表達式與委託直接相關,當參數是委託類型時,就可使用Lambda表達式引用方法.sql
委託的本質 是class,能夠查看IL代碼數據庫
委託聲明決定了可由該委託引用的方法,委託可指向一個與其具備相同標籤的方法。
例如,假設有一個委託:express
public delegate int MyDelegate (string s);
理解委託,須要理解如下幾點:編程
實例化委託:
給委託變量賦值時,千萬不要將方法帶括號,帶括號意味賦值是方法的結果,因此直接賦值方法名。c#
Action:它表示引用一個void返回類型的方法。這個委託類存在不一樣的變體,能夠傳遞至多16種不一樣的參數類型。數組
Action | 說明 |
---|---|
Action | 調用沒有參數的方法 |
Action<in T1,> | 調用帶一個參數的方法, T1後面不帶逗號,由於網頁顯示不出來,因此加上去 |
Action<in T1,in T2> | 調用帶兩個參數的方法, |
Action<in T1,in T2,in T3> | 調用了帶3個參數的方法 |
...... | 直到調用了帶16個參數的方法 |
Func:它表示引用一個必有返回類型的方法。這個委託類存在不一樣的變體,能夠傳遞至多16種不一樣的參數類型,但必須有一個返回值。安全
Func | 說明 |
---|---|
Func<out TResult,> | 調用沒有參數的方法,可是有返回值,TResult後面不帶逗號,由於網頁顯示不出來,因此加上去 |
Action<in T,out TResult > | 調用帶一個參數的方法,返回一個值 |
Action<in T1,in T2,out TResult> | 調用帶兩個參數的方法,返回一個值 |
Action<in T1,in T2,in T3,out TResult> | 調用了帶3個參數的方法,返回一個值 |
...... | 直到調用了帶16個參數的方法 |
Predicate:表示定義一組條件並肯定指定對象是否符合這些條件的方法。
此委託由 Array 和 List 類的幾種方法使用,用於在集合中搜索元素。數據結構
public delegate bool Predicate<in T>(T obj);
類型參數:T
要比較的對象的類型。參數:obj 。要按照由此委託表示的方法中定義的條件進行比較的對象。
返回值:System.Boolean。若是 obj 符合由此委託表示的方法中定義的條件,則爲 true;不然爲 false。
繼承:ObjectDelegatePredicate
示例:
using System; using System.Drawing; public class Example { public static void Main() { // Create an array of Point structures. Point[] points = { new Point(100, 200), new Point(150, 250), new Point(250, 375), new Point(275, 395), new Point(295, 450) }; // Define the Predicate<T> delegate. Predicate<Point> predicate = FindPoints; // Find the first Point structure for which X times Y // is greater than 100000. Point first = Array.Find(points, predicate); // Display the first structure found. Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y); } private static bool FindPoints(Point obj) { return obj.X * obj.Y > 100000; } } // The example displays the following output: // Found: X = 275, Y = 395
下面的示例等同於上一示例中,只不過它使用 lambda 表達式來表示Predicate
using System; using System.Drawing; public class Example { public static void Main() { // Create an array of Point structures. Point[] points = { new Point(100, 200), new Point(150, 250), new Point(250, 375), new Point(275, 395), new Point(295, 450) }; // Find the first Point structure for which X times Y // is greater than 100000. Point first = Array.Find(points, x => x.X * x.Y > 100000 ); // Display the first structure found. Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y); } } // The example displays the following output: // Found: X = 275, Y = 395
若是要調用多個方法,就須要屢次顯式調用這個委託。可是委託也能夠包含多個方法,這種委託稱爲多播委託。
若是調用多播委託就能夠按順序連續調用多個方法。爲此,委託的簽名就必須返回void;不然,就只能獲得委託調用的最後一個方法的結果。
例如:
因此當調用委託3時,委託1和委託2會同時執行,與第一種實現多播委託一個道理。
可是多播委託還有一個缺陷,一旦一個委託發生異常,其餘委託都會中止。爲了不這個問題,應本身迭代方法列表。Delegate類定義GetInvocationList()方法,它返回一個Delegate對象數組。如今可使用這個委託調用與委託直接相關的方法,捕獲異常,並繼續下一次迭代。(.net core 中間件)
到目前爲止,要想使委託工做,方法名必須已經存在。但還有另一種使用委託的方式:匿名方法。匿名方法是用做委託的參數的一段代碼,用匿名方法定義委託的語法與前面的定義並無區別,但在實例化委託時,就有了區別。下面是一個很是簡單的代碼,它說明了如何使用匿名方法:
Func<string,string> anonDel=delegate(string param) { param+=mid; param+=" and this was added to the string."; return param; }
使用匿名方法的規則很明顯,它前面是關鍵字delegate,後面是一個字符串參數.
使用匿名方法
匿名方法語法
delegate (parameters ){implementationcode};
關鍵字 參數 方法體
匿名方法不會聲明返回值類型。可是匿名方法返回值類型必須和委託返回值同樣。
進階
咱們可使圓括號爲空,或省略圓括號來簡化匿名方法的參數列表。可是僅在下面兩項都爲真的狀況下才能夠這麼作。
class Program { delegate int otherdel(int param); public static void Main() { otherdel del = delegate { cleanup(); printMessage(); }; } }
params參數
若是委託參數包含params參數,那麼params關鍵字就會被匿名方法的參數列表忽略。以下:
delegate int otherdel(int x,params int y); otherdel del = delegate(int x,int y) { };
從c#3.0開始,就可使用一種新語法把實現代碼賦予委託:Lambda表達式
只要有委託參數類型的地方,就可使用Lambda表達式。前面使用匿名方法的例子能夠改成使用Lambda表達式。
Lambda表達式的語法比匿名方法簡單.若是所調用的方法有參數,且不須要參數,匿名方法的語法就比較簡單,由於這樣不須要提供參數
定義:Labmda表達式有幾種定義參數的方式.
Func<string,string> oneParam=s=>string.Format("change uppercase{0}",s.ToUpper()); Console.WriteLine(oneParam("test"));
Func<double,double,double> twoParams=(x,y)=>x*y; console.WriteLine(twoParams(3,2));
func<double,double,double> twoParamsWithTypes=(double x,double y)=>x*y ; Console.Write(twoParamsWithTypes(2,3));
Func<double,double> square=x=>{ return x*x };
閉包
經過Lambda表達式能夠訪問表達式外部的變量,這稱爲閉包。閉包是一個很是好的功能,但若是未正確使用,也會很是危險。 多線程使用時就會出兮兮,由於不知道此時外部變量是什麼。
方法泛型集合定義:List<Func<int>>();
lambda表達式是被視爲一個對象的代碼(表達式或語句塊)的塊。它能夠做爲參數傳遞給方法,也能夠經過方法調用返回。Lambda表達式普遍用於:
lambda表達式除了能夠分配給委託類型外,還能夠分配給表達式樹類型,如:
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x; Console.WriteLine(e); // Output: // x => (x * x)
或者能夠直接將其做爲方法參數傳遞:
int[] numbers = { 2, 3, 4, 5 }; var squaredNumbers = numbers.Select(x => x * x); Console.WriteLine(string.Join(" ", squaredNumbers)); // Output: // 4 9 16 25
當您使用基於方法的語法來調用System.Linq.Enumerable類中的Enumerable.Select方法時(就像在LINQ to Objects和LINQ to XML中那樣),參數是委託類型System.Func <T,TResult>。lambda表達式是建立該委託的最便捷方式。
當您在System.Linq.Queryable類中調用Queryable.Select方法時(就像在LINQ to SQL中那樣),參數類型是表達式樹類型。一樣,lambda表達式只是構造表達式樹的一種很是簡潔的方式。lambda容許調用看起來相似,但實際上從lambda建立的對象類型是不一樣的。Expression<Func<TSource,TResult>>Select
在=>運算符右側具備表達式的lambda表達式稱爲表達式lambda。表達式lambda普遍用於構建表達樹。表達式lambda返回表達式的結果,並採用如下基本形式:
(inputParameters) => expression
只有當lambda有一個輸入參數時,括號纔是可選的。不然他們是必需的。
使用空括號指定零輸入參數:
Action line = () => Console.WriteLine();
兩個或多個輸入參數用括在括號中的逗號分隔:
Func<int, int, bool> testForEquality = (x, y) => x == y;
有時,編譯器沒法推斷輸入類型。您能夠顯式指定類型,如如下示例所示:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
輸入參數類型必須所有顯式或所有隱式; 不然,會發生CS0748編譯器錯誤。
表達式lambda的主體能夠包含方法調用。可是,若是要建立在.NET公共語言運行庫的上下文以外計算的表達式樹(例如在SQL Server中),則不該在lambda表達式中使用方法調用。這些方法在.NET公共語言運行庫的上下文以外沒有任何意義。
語句lambda相似於表達式lambda,除了語句括在括號中:
(input-parameters) => { statement; }
語句lambda的主體能夠包含任意數量的語句; 然而,在實踐中一般不超過兩個或三個。
Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("World"); // Output: // Hello World!
語句lambda與匿名方法同樣,不能用於建立表達式樹。
您可使用async和await關鍵字輕鬆建立包含異步處理的lambda表達式和語句。例如,如下Windows窗體示例包含一個調用和等待異步方法的事件處理程序ExampleMethodAsync。
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += button1_Click; } private async void button1_Click(object sender, EventArgs e) { await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; } private async Task ExampleMethodAsync() { // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); } }
可使用異步lambda添加相同的事件處理程序。要添加此處理程序,請async在lambda參數列表以前添加修飾符,如如下示例所示:
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += async (sender, e) => { await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; }; } private async Task ExampleMethodAsync() { // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); } }
從C#7.0開始,C#語言爲元組提供內置支持。你能夠提供一個元組做爲lambda表達式的參數,你的lambda表達式也能夠返回一個元組。在某些狀況下,C#編譯器使用類型推斷來肯定元組組件的類型。
您能夠經過在括號中包含逗號分隔的組件列表來定義元組。下面的示例使用帶有三個組件的元組將一系列數字傳遞給lambda表達式,該表達式將每一個值加倍並返回包含三個包含乘法結果的組件的元組。
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3); var numbers = (2, 3, 4); var doubledNumbers = doubleThem(numbers); Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}"); // Output: // The set (2, 3, 4) doubled: (4, 6, 8)
除了其餘實現以外,LINQ to Objects還有一個輸入參數,其類型是Func
當參數類型是Expression
如下示例使用Count標準查詢運算符:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(n => n % 2 == 1); Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
編譯器能夠推斷輸入參數的類型,也能夠顯式指定它。這個特殊的lambda表達式計算那些整數(n),當除以2時,餘數爲1。
如下示例生成一個序列,其中包含numbers數組中位於9以前的全部元素,由於這是序列中不符合條件的第一個數字:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6); Console.WriteLine(string.Join(" ", firstNumbersLessThanSix)); // Output: // 5 4 1 3
編寫lambda時,一般沒必要爲輸入參數指定類型,由於編譯器能夠根據lambda正文,參數類型和C#語言規範中描述的其餘因素推斷類型。對於大多數標準查詢運算符,第一個輸入是源序列中元素的類型。若是要查詢IEnumerable
customers.Where(c => c.City == "London");
lambdas類型推斷的通常規則以下:
lambda的返回值(若是有的話)必須能夠隱式轉換爲委託的返回類型。
請注意,lambda表達式自己沒有類型,由於公共類型系統沒有「lambda表達式」的內在概念。然而,有時很是方便地談論lambda表達式的「類型」。在這些狀況下,類型引用lambda表達式轉換爲的委託類型或表達式類型。
Lambda能夠引用外部變量(請參閱匿名方法),這些變量位於定義lambda表達式的方法的範圍內,或者在包含lambda表達式的類型的範圍內。以這種方式捕獲的變量被存儲用於lambda表達式,即便變量不然將超出範圍並被垃圾收集。必須明確賦值外部變量,而後才能在lambda表達式中使用它。如下示例演示瞭如下規則:
public static class VariableScopeWithLambdas { public class VariableCaptureGame { internal Action<int> updateCapturedLocalVariable; internal Func<int, bool> isEqualToCapturedLocalVariable; public void Run(int input) { int j = 0; updateCapturedLocalVariable = x => { j = x; bool result = j > input; Console.WriteLine($"{j} is greater than {input}: {result}"); }; isEqualToCapturedLocalVariable = x => x == j; Console.WriteLine($"Local variable before lambda invocation: {j}"); updateCapturedLocalVariable(10); Console.WriteLine($"Local variable after lambda invocation: {j}"); } } public static void Main() { var game = new VariableCaptureGame(); int gameInput = 5; game.Run(gameInput); int jTry = 10; bool result = game.isEqualToCapturedLocalVariable(jTry); Console.WriteLine($"Captured local variable is equal to {jTry}: {result}"); int anotherJ = 3; game.updateCapturedLocalVariable(anotherJ); bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ); Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}"); } // Output: // Local variable before lambda invocation: 0 // 10 is greater than 5: True // Local variable after lambda invocation: 10 // Captured local variable is equal to 10: True // 3 is greater than 5: False // Another lambda observes a new value of captured variable: True }
如下規則適用於lambda表達式中的變量做用域:
定義 :將強類型lambda表達式表示爲表達式樹形式的數據結構。這個類不能被繼承。
場景: 目前被大量運行在linq to sql 中。將表達式樹轉換成表達式,而後轉換成SQL。
public sealed class Expression<TDelegate> : System.Linq.Expressions.LambdaExpression
下面的代碼示例演示如何將lambda表達式表示爲委託形式的可執行代碼和表達式樹形式的數據。它還演示瞭如何使用Compile方法將表達式樹轉換回可執行代碼。
// Lambda expression as executable code. Func<int, bool> deleg = i => i < 5; // Invoke the delegate and display the output. Console.WriteLine("deleg(4) = {0}", deleg(4)); // Lambda expression as data in the form of an expression tree. System.Linq.Expressions.Expression<Func<int, bool>> expr = i => i < 5; // Compile the expression tree into executable code. Func<int, bool> deleg2 = expr.Compile(); // Invoke the method and print the output. Console.WriteLine("deleg2(4) = {0}", deleg2(4)); /* This code produces the following output: deleg(4) = True deleg2(4) = True */
表達式樹是lambda表達式的內存數據表示。表達式樹使lambda表達式的結構透明和顯式。您能夠像處理任何其餘數據結構同樣與表達式樹中的數據進行交互。
將表達式視爲數據結構的能力使API可以以能夠自定義方式檢查,轉換和處理的格式接收用戶代碼。例如,LINQ to SQL數據訪問實現使用此工具將表達式樹轉換爲可由數據庫評估的Transact-SQL語句。
Queryable類中定義的許多標準查詢運算符都有一個或多個Expression
所述的NodeType一個的表達
使用Lambda
方法