C#学习基本概念之匿名方法及Lambda表达式
在 2.0 之前的 C# 版本中,聲明委托的唯一方法是使用命名方法。 ?C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表達式取代了匿名方法,作為編寫內聯代碼的首選方式。 ? 不過,本主題中有關匿名方法的信息同樣也適用于 Lambda 表達式。 ?有一種情況下,匿名方法提供了 Lambda 表達式中所沒有的功能。 ? 您可使用匿名方法來忽略參數列表。 ?這意味著匿名方法可轉換為具有各種簽名的委托。 ?這對于 Lambda 表達式來說是不可能的。 ?有關 lambda 表達式的更多特定信息,請參見 Lambda 表達式。
要將代碼塊傳遞為委托參數,創建匿名方法則是唯一的方法。 ?這里是兩個示例:
button1.Click?+=?delegate(System.Object?o,?System.EventArgs?e) {?System.Windows.Forms.MessageBox.Show("Click!");?}; //?Create?a?delegate.delegate?void?Del(int?x);????//?Instantiate?the?delegate?using?an?anonymous?method.Del?d?=?delegate(int?k)?{?/*?...?*/?};通過使用匿名方法,由于您不必創建單獨的方法,因此減少了實例化委托所需的編碼系統開銷。
例如,如果創建方法所需的系統開銷是不必要的,則指定代碼塊(而不是委托)可能非常有用。 ?啟動新線程即是一個很好的示例。 ?無需為委托創建更多方法,線程類即可創建一個線程并且包含該線程執行的代碼。
void?StartThread(){System.Threading.Thread?t1?=?new?System.Threading.Thread(delegate(){System.Console.Write("Hello,?");System.Console.WriteLine("World!");});t1.Start();}備注
匿名方法的參數的范圍是“匿名方法塊”。
如果目標在塊外部,那么,在匿名方法塊內使用跳轉語句(如 goto、break 或 continue)是錯誤的。 ?如果目標在塊內部,在匿名方法塊外部使用跳轉語句(如 goto、break 或 continue)也是錯誤的。
如果局部變量和參數的范圍包含匿名方法聲明,則該局部變量和參數稱為該匿名方法的“外部”變量。 ?例如,下面代碼段中的 n 即是一個外部變量:
?int?n?=?0;Del?d?=?delegate()?{?System.Console.WriteLine("Copy?#:{0}",?++n);?};外部變量的引用n被認為是捕獲在創建委托時。 ?與本地變量不同,捕獲的變量的生存期內擴展,直到引用該匿名方法委托被垃圾回收。
匿名方法不能訪問外部范圍的 ref 或 out 參數。
在“匿名方法塊”中不能訪問任何不安全代碼。
在 is 運算符的左側不允許使用匿名方法。
示例
下面的示例演示實例化委托的兩種方法:
使委托與匿名方法關聯。
使委托與命名方法 (DoWork) 關聯。
兩種方法都會在調用委托時顯示一條消息。
?//?Declare?a?delegate.delegate?void?Printer(string?s);????class?TestClass{????????static?void?Main(){????????????//?Instantiate?the?delegate?type?using?an?anonymous?method.Printer?p?=?delegate(string?j){System.Console.WriteLine(j);};????????????//?Results?from?the?anonymous?delegate?call.p("The?delegate?using?the?anonymous?method?is?called.");????????????//?The?delegate?instantiation?using?a?named?method?"DoWork".p?=?new?Printer(TestClass.DoWork);????????????//?Results?from?the?old?style?delegate?call.p("The?delegate?using?the?named?method?is?called.");}????????//?The?method?associated?with?the?named?delegate.static?void?DoWork(string?k){System.Console.WriteLine(k);}}????/*?Output:The?delegate?using?the?anonymous?method?is?called.The?delegate?using?the?named?method?is?called.*/備注:摘自https://msdn.microsoft.com/zh-cn/library/0yw3tz5k.aspx
Lambda:
若要了解有關 Visual Studio 2017 RC 的最新文檔,請參閱 Visual Studio 2017 RC 文檔。
Lambda 表達式是一種可用于創建委托或表達式目錄樹類型的匿名函數。 通過使用 lambda 表達式,可以寫入可作為參數傳遞或作為函數調用值返回的本地函數。 Lambda 表達式對于編寫 LINQ 查詢表達式特別有用。
若要創建 Lambda 表達式,需要在 Lambda 運算符 => 左側指定輸入參數(如果有),然后在另一側輸入表達式或語句塊。 例如,lambda 表達式 x => x * x 指定名為 x 的參數并返回 x 的平方值。 如下面的示例所示,你可以將此表達式分配給委托類型:
delegate?int?del(int?i);?? static?void?Main(string[]?args)?? {??del?myDelegate?=?x?=>?x?*?x;??int?j?=?myDelegate(5);?//j?=?25??}若要創建表達式目錄樹類型:
using?System.Linq.Expressions;??namespace?ConsoleApplication1?? {??class?Program??{??static?void?Main(string[]?args)??{??Expression<del>?myET?=?x?=>?x?*?x;??}??}?? }=> 運算符具有與賦值運算符 (=) 相同的優先級并且是右結合運算(參見“運算符”文章的“結合性”部分)。
Lambda 在基于方法的 LINQ 查詢中用作標準查詢運算符方法(如 Where<TSource>)的參數。
使用基于方法的語法在 Where<TSource> 類中調用 Enumerable 方法時(如在 LINQ to Objects 和 LINQ to XML 中一樣),參數是委托類型 System.Func<T,TResult>。 使用 Lambda 表達式創建該委托最為方便。 例如,當你在 System.Linq.Queryable 類中調用相同的方法時(如在 LINQ to SQL 中一樣),參數類型為 System.Linq.Expressions.Expression<Func>,其中 Func 是最多具有十六個輸入參數的任何一個 Func 委托。 同樣,Lambda 表達式只是一種非常簡潔的構造該表達式目錄樹的方式。 盡管事實上通過 Lambda 創建的對象具有不同的類型,但 Lambda 使得 Where 調用看起來類似。
在上一個示例中,請注意委托簽名具有一個 int 類型的隱式類型輸入參數,并返回 int。 可以將 Lambda 表達式轉換為該類型的委托,因為該表達式也具有一個輸入參數 (x),以及一個編譯器可隱式轉換為 int 類型的返回值。 (以下幾節中將對類型推理進行詳細討論。) 使用輸入參數 5 調用委托時,它將返回結果 25。
在 is 或 as 運算符的左側不允許使用 Lambda。
適用于匿名方法的所有限制也適用于 Lambda 表達式。 有關更多信息,請參見匿名方法。
表達式 lambda
表達式位于 => 運算符右側的 lambda 表達式稱為“表達式 lambda”。 表達式 lambda 廣泛用于表達式樹的構造。 表達式 lambda 會返回表達式的結果,并采用以下基本形式:
(input?parameters)?=>?expression僅當 lambda 只有一個輸入參數時,括號才是可選的;否則括號是必需的。 括號內的兩個或更多輸入參數使用逗號加以分隔:
(x,?y)?=>?x?==?y有時,編譯器難以或無法推斷輸入類型。 如果出現這種情況,你可以按以下示例中所示方式顯式指定類型:
(int?x,?string?s)?=>?s.Length?>?x使用空括號指定零個輸入參數:
()?=>?SomeMethod()在上一個示例中,請注意表達式 Lambda 的主體可以包含一個方法調用。 但是,如果要創建在 .NET Framework 之外計算的表達式目錄樹(例如,在 SQL Server 中),則不應在 lambda 表達式中使用方法調用。 在 .NET 公共語言運行時上下文之外,方法將沒有任何意義。
語句 lambda 與表達式 lambda 表達式類似,只是語句括在大括號中:
(input?parameters)?=>?{statement;}語句 lambda 的主體可以包含任意數量的語句;但是,實際上通常不會多于兩個或三個。
delegate?void?TestDelegate(string?s);?? …?? TestDelegate?myDel?=?n?=>?{?string?s?=?n?+?"?"?+?"World";?Console.WriteLine(s);?};?? myDel("Hello");像匿名方法一樣,語句 lambda 也不能用于創建表達式目錄樹。
異步 lambda
通過使用 async 和 await 關鍵字,你可以輕松創建包含異步處理的 lambda 表達式和語句。 例如,下面的 Windows 窗體示例包含一個調用和等待異步方法 ExampleMethodAsync 的事件處理程序。
public?partial?class?Form1?:?Form?? {??public?Form1()??{??InitializeComponent();??}??private?async?void?button1_Click(object?sender,?EventArgs?e)??{??//?ExampleMethodAsync?returns?a?Task.??await?ExampleMethodAsync();??textBox1.Text?+=?"\r\nControl?returned?to?Click?event?handler.\r\n";??}??async?Task?ExampleMethodAsync()??{??//?The?following?line?simulates?a?task-returning?asynchronous?process.??await?Task.Delay(1000);??}?? }你可以使用異步 lambda 添加同一事件處理程序。 若要添加此處理程序,請在 lambda 參數列表前添加一個 async 修飾符,如下例所示。
public?partial?class?Form1?:?Form?? {??public?Form1()??{??InitializeComponent();??button1.Click?+=?async?(sender,?e)?=>??{??//?ExampleMethodAsync?returns?a?Task.??await?ExampleMethodAsync();??textBox1.Text?+=?"\r\nControl?returned?to?Click?event?handler.\r\n";??};??}??async?Task?ExampleMethodAsync()??{??//?The?following?line?simulates?a?task-returning?asynchronous?process.??await?Task.Delay(1000);??}?? }有關如何創建和使用異步方法的詳細信息,請參閱使用 Async 和 Await 的異步編程。
帶有標準查詢運算符的 lambda
許多標準查詢運算符都具有輸入參數,其類型是泛型委托系列 Func<T,TResult> 中的一種。 這些委托使用類型參數來定義輸入參數的數量和類型,以及委托的返回類型。Func 委托對于封裝用戶定義的表達式非常有用,這些表達式將應用于一組源數據中的每個元素。 例如,請考慮以下委托類型:
public?delegate?TResult?Func<TArg0,?TResult>(TArg0?arg0)可以將委托實例化為 Func<int,bool> myFunc,其中 int 是輸入參數,bool 是返回值。 返回值始終在最后一個類型參數中指定。Func<int, string, bool> 定義包含兩個輸入參數(int 和 string)且返回類型為 bool 的委托。 當調用下面的 Func 委托時,該委托將返回 true 或 false 以指示輸入參數是否等于 5:
Func<int,?bool>?myFunc?=?x?=>?x?==?5;?? bool?result?=?myFunc(4);?//?returns?false?of?course當參數類型為 Expression<Func> 時,你也可以提供 Lambda 表達式,例如在 System.Linq.Queryable 內定義的標準查詢運算符中。 如果指定 Expression<Func> 參數,lambda 將編譯為表達式目錄樹。
此處顯示了一個標準查詢運算符,Count<TSource> 方法:
int[]?numbers?=?{?5,?4,?1,?3,?9,?8,?6,?7,?2,?0?};?? int?oddNumbers?=?numbers.Count(n?=>?n?%?2?==?1);編譯器可以推斷輸入參數的類型,或者你也可以顯式指定該類型。 這個特殊 lambda 表達式將計算那些除以 2 時余數為 1 的整數的數量 (n)。
下面一行代碼將生成一個序列,其中包含 numbers 數組中在 9 左側的所有元素,因為它是序列中第一個不滿足條件的數字:
var?firstNumbersLessThan6?=?numbers.TakeWhile(n?=>?n?<?6);此示例展示了如何通過將輸入參數括在括號中來指定多個輸入參數。 該方法將返回數字數組中的所有元素,直至遇到一個值小于其位置的數字為止。 不要將 lambda 運算符 (=>) 與大于等于運算符 (>=) 混淆。
var?firstSmallNumbers?=?numbers.TakeWhile((n,?index)?=>?n?>=?index);Lambda 中的類型推理
在編寫 lambda 時,通常不必為輸入參數指定類型,因為編譯器可以根據 lambda 主體、參數的委托類型以及 C# 語言規范中描述的其他因素來推斷類型。 對于大多數標準查詢運算符,第一個輸入是源序列中的元素類型。 因此,如果要查詢 IEnumerable<Customer>,則輸入變量將被推斷為 Customer 對象,這意味著你可以訪問其方法和屬性:
customers.Where(c?=>?c.City?==?"London");Lambda 的一般規則如下:
Lambda 包含的參數數量必須與委托類型包含的參數數量相同。
Lambda 中的每個輸入參數必須都能夠隱式轉換為其對應的委托參數。
Lambda 的返回值(如果有)必須能夠隱式轉換為委托的返回類型。
請注意,lambda 表達式本身沒有類型,因為常規類型系統沒有“Lambda 表達式”這一內部概念。 但是,有時以一種非正式的方式談論 lambda 表達式的“類型”會很方便。 在這些情況下,類型是指委托類型或 lambda 表達式所轉換到的 Expression 類型。
Lambda 表達式中的變量范圍
在定義 lambda 函數的方法內或包含 lambda 表達式的類型內,Lambda 可以引用范圍內的外部變量(請參閱匿名方法)。 以這種方式捕獲的變量將進行存儲以備在 lambda 表達式中使用,即使在其他情況下,這些變量將超出范圍并進行垃圾回收。 必須明確地分配外部變量,然后才能在 lambda 表達式中使用該變量。 下面的示例演示這些規則:
delegate?bool?D();?? delegate?bool?D2(int?i);??class?Test?? {??D?del;??D2?del2;??public?void?TestMethod(int?input)??{??int?j?=?0;??//?Initialize?the?delegates?with?lambda?expressions.??//?Note?access?to?2?outer?variables.??//?del?will?be?invoked?within?this?method.??del?=?()?=>?{?j?=?10;??return?j?>?input;?};??//?del2?will?be?invoked?after?TestMethod?goes?out?of?scope.??del2?=?(x)?=>?{return?x?==?j;?};??//?Demonstrate?value?of?j:??//?Output:?j?=?0???//?The?delegate?has?not?been?invoked?yet.??Console.WriteLine("j?=?{0}",?j);????????//?Invoke?the?delegate.??bool?boolResult?=?del();??//?Output:?j?=?10?b?=?True??Console.WriteLine("j?=?{0}.?b?=?{1}",?j,?boolResult);??}??static?void?Main()??{??Test?test?=?new?Test();??test.TestMethod(5);??//?Prove?that?del2?still?has?a?copy?of??//?local?variable?j?from?TestMethod.??bool?result?=?test.del2(10);??//?Output:?True??Console.WriteLine(result);??Console.ReadKey();??}?? }下列規則適用于 lambda 表達式中的變量范圍:
捕獲的變量將不會被作為垃圾回收,直至引用變量的委托符合垃圾回收的條件。
在外部方法中看不到 lambda 表達式內引入的變量。
Lambda 表達式無法從封閉方法中直接捕獲 ref 或 out 參數。
Lambda 表達式中的返回語句不會導致封閉方法返回。
如果跳轉語句的目標在塊外部,則 lambda 表達式不能包含位于 lambda 函數內部的 goto 語句、break 語句或 continue 語句。 同樣,如果目標在塊內部,則在 lambda 函數塊外部使用跳轉語句也是錯誤的。
轉載于:https://blog.51cto.com/jiaojusuimu/1877137
總結
以上是生活随笔為你收集整理的C#学习基本概念之匿名方法及Lambda表达式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 跨域调用报表展现页面的flash打印方法
- 下一篇: 机器学习-损失函数 (转)