C#進階之全面解析Lambda表示式

愛在846發表於2019-06-21

引言

在實際的專案中遇到一個問題,我們經常在網上搜尋複製貼上,其中有些程式碼看著非常的簡潔,比如Lambda表示式,但是一直沒有去深入瞭解它的由來,以及具體的使用方法,所以在使用的時候比較模糊,其次,程式設計涉及面比較廣,我們不可能每個方面都去精通了解,但經常運到的一些東西,必須瞭解其具體使用方法及使用場景,才能書寫出優美、簡潔、可讀性強的程式碼。筆者通過搜尋、整理資料及測試程式碼,詳細的介紹Lambda 表示式的用法。

Lambda 表示式概念

“Lambda 表示式”(lambda expression)是一個匿名函式,可以表示為委託的程式碼,或者表示為表示式樹的程式碼,它所表示的表示式樹可以編譯為委託。 Lambda 表示式的特定委託型別取決於其引數和返回值。不返回值的 Lambda 表示式對應於 Action 委託,具體取決於其引數數量。 返回值的 Lambda 表示式對應於 Func 委託,具體取決於其引數數量。

Lambda 表示式廣泛用於:

  • 將要執行的程式碼傳遞給非同步方法,例如 Task.Run(Action)。

  • 編寫 LINQ 查詢表示式。

  • 建立表示式樹。

C# 中委託的演變

在 C# 1.0 中,通過使用在程式碼中其他位置定義的方法顯式初始化委託來建立委託的例項。 C# 2.0 引入了匿名方法的概念,作為一種編寫可在委託呼叫中執行的未命名內聯語句塊的方式。 C# 3.0 引入了 Lambda 表示式,這種表示式與匿名方法的概念類似,但更具表現力並且更簡練。 這兩個功能統稱為匿名函式。 通常,面向 .NET Framework 3.5 及更高版本的應用程式應使用 lambda 表示式。

C#1.0中委託的實現,程式碼如下:

 delegate int CalculateHandler(int x, int y);
 private int Sum(int x, int y)
 {
    return x + y;
 }
 public void Test()
 {
    CalculateHandler sumHandler =new CalculateHandler(Sum);
    MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3
}

C#2.0中匿名方法的實現,程式碼如下:

delegate int CalculateHandler(int x, int y);
public void Test()
{
    CalculateHandler sumHandler = delegate(int x, int y) { return x + y; };
    MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3
}

C#3.0中Lambda 表示式的實現,程式碼如下:

 delegate int CalculateHandler(int x, int y);
 public void Test()
 {
      CalculateHandler sumHandler = (x, y) => x + y;
      MessageBox.Show(sumHandler(1, 2).ToString());//輸入結果3
 }

由此可以看出微軟的一步步升級,帶給我們的是程式設計上的優美,簡潔,可讀性強,因此作為程式設計師我們要一直處於學習的路上。

Lambda 表示式使用

C#的Lambda 表示式都使用 Lambda 運算子 =>,該運算子讀為“goes to”, 若要建立 Lambda 表示式,需要在 lambda 運算子左側指定輸入引數(如果有),然後在另一側輸入表示式或語句塊。 例如,單行 Lambda 表示式 x => x * x 指定名為 x 的引數並返回 x 的平方值。

在介紹Lambda 表示式使用之前我們先了解.Net為我們定義好的Action<T>和Func<T>兩個泛型委託。

Action<T>泛型委託

Action<T>委託表示引用一個返回型別為Void的方法。這個委託存在不同的變體,可以傳遞之多16個不同的引數型別。同時,沒有泛型引數的Action類可以呼叫沒有引數的方法。例如,Action<in T>表示有一個輸入引數的方法,Action<in T1,in T2>表示有兩個輸入引數的方法。

Func<T>泛型委託

Func<T>可以以類似的方法使用。不過Func<T>允許呼叫帶返回引數的方法。Func<T>也有不同的變體,之多可以傳遞16個引數和一個返回型別。例如:Func<out TResult>委託型別可以無參的帶返回型別的方法,Func<in T1,inT2,out Tresult>表示帶兩個引數和一個返回型別的方法。

Func<T>可以表示帶輸出的方法,T可以有多個,且只有最後一個表示輸出即最後一個是返回型別。Func<in T1,inT2,out Tresult>中的字元in、out在實際程式碼中是不會出現的。

表示式 Lambda

表示式位於 => 運算子右側的 Lambda 表示式稱為“表示式 lambda”。具體形式:(input-parameters) => expression,表示式 lambda 會返回表示式的結果。

具體事例,程式碼如下:

 public Action SuccessPrompt =() => MessageBox.Show("執行成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); //沒有引數,括號不能省略
 public Func<int, int, bool> Compare = (x, y) => x > y;//判斷x是否大於y

語句 Lambda

語句 Lambda 與表示式 lambda 表示式類似,只是語句括在大括號中,具體形式:(input-parameters) => { statement; }。語句 lambda 的主體可以包含任意數量的語句;但是,實際上通常不會多於兩個或三個。

具體事例程式碼如下:

 public Action<string> Prompt = prompt =>
        {
            MessageBox.Show(prompt, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        };//僅當 Lambda 只有一個輸入引數時,括號才是可選的;否則括號是必需的

非同步 Lambda

通過使用 async 和 await 關鍵字,你可以輕鬆建立包含非同步處理的 lambda 表示式和語句。其中async 和 await 關鍵字是在 C# 5 中引入的。

await關鍵字

await 運算子應用於非同步方法中的任務,在方法的執行中插入掛起點,直到所等待的任務完成。 任務表示正在進行的工作。

await 僅可用於由 async 關鍵字修改的非同步方法中。 使用 async 修飾符定義並且通常包含一個或多個 await 表示式的這類方法稱為非同步方法。

async修飾符

使用 async 修飾符可將方法、lambda 表示式或匿名方法指定為非同步。 如果對方法或表示式使用此修飾符,則其稱為非同步方法。

使用非同步 lambda 新增事件處理程式。 若要新增此處理程式,請在 lambda 引數列表前新增 async 修飾符,程式碼如下:

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);//Task.Delay方法只會延緩非同步方法中後續部分執行時間,當程式執行到await表達時,一方面會立即返回撥用方法,執行呼叫方法中的剩餘部分,這一部分程式的執行不會延長。另一方面根據Delay()方法中的引數,延時對非同步方法中後續部分的執行。
    }
}

Lambda 表示式和元組

自 C# 7.0(對應 .NET Framework4.7和Visual Studio 2017 )起,C# 語言提供對元組的內建支援。 可以提供一個元組作為 Lambda 表示式的引數,同時 Lambda 表示式也可以返回元組。 在某些情況下,C# 編譯器使用型別推理來確定元組元件的型別。可通過用括號括住用逗號分隔的元件列表來定義元組,通常,元組欄位命名為 Item1Item2 等等。但是,可以使用命名元件定義元組。

事例程式碼如下:

public void Test1()
{
    Func<(int, int, int), (int, int, int)> doubleItem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
    var itemList = (1, 2, 3);
    var resultDItemList = (itemList);//結果為[1, 2, 9]
}

public void Test2()
{
    Func<(int x, int y, int z), (int, int, int)> doubleItem = ns => (2 * ns.x, 2 * ns.y, 2 * ns.z);
    var itemList = (1, 2, 3);
    var resultDItemList = (itemList);//結果為[1, 2, 9]
}

含標準查詢運算子的 Lambda

在其他實現中,LINQ to Objects 有一個輸入引數,其型別是泛型委託 Func<TResult> 系列中的一種。 這些委託使用型別引數來定義輸入引數的數量和型別,以及委託的返回型別。 Func 委託對於封裝使用者定義的表示式非常有用,這些表示式將應用於一組源資料中的每個元素。

標準查詢運算子是組成 LINQ 模式的方法。 這些方法中的大多數都作用於序列;其中序列指其型別實現 IEnumerable<T> 介面或 IQueryable<T> 介面的物件。 標準查詢運算子提供包括篩選、投影、聚合、排序等在內的查詢功能。

共有兩組 LINQ 標準查詢運算子,一組作用於型別 IEnumerable<T> 的物件,另一組作用於型別 IQueryable<T> 的物件。 構成每個集合的方法分別是 Enumerable 和 Queryable 類的靜態成員。 這些方法被定義為作為方法執行目標的型別的擴充套件方法。 這意味著可以使用靜態方法語法或例項方法語法來呼叫它們。

具體事例程式碼如下:

        public class Sutdent
        {
            public string Id { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }
        }
        static void Main(string[] args)
        {
            List<Sutdent> studentList = new List<Sutdent>();
            studentList.Add(new Sutdent {Id = "001", Name = "張三", Age = 18});
            studentList.Add(new Sutdent {Id = "002", Name = "李四", Age = 19});
            studentList.Add(new Sutdent {Id = "003", Name = "王五", Age = 16});
            studentList.Add(new Sutdent {Id = "004", Name = "趙六", Age = 17});
            
            List<Sutdent> list1 = studentList.FindAll(st => st.Age > 17);//選擇年齡大於17的所有學生
            List<Sutdent> list2 = studentList.Where(st => st.Age > 17).ToList();//選擇年齡大於17的所有學生
            studentList.Sort((st1,st2)=>st2.Age-st1.Age);//按Age降序排列
            List<string> list3 = studentList.Select(st => st.Name).ToList();//選擇列表中的所有名字
           
        }

Lambda 表示式中的型別推理

編寫 Lambda 時,通常不必為輸入引數指定型別,因為編譯器可以根據 Lambda 主體、引數型別以及 C# 語言規範中描述的其他因素來推斷型別。

lambda 型別推理的一般規則如下:

  • Lambda 包含的引數數量必須與委託型別包含的引數數量相同。

  • Lambda 中的每個輸入引數必須都能夠隱式轉換為其對應的委託引數。

  • Lambda 的返回值(如果有)必須能夠隱式轉換為委託的返回型別。

總結

通過上邊的講解,我們可以看出Lambda表示式的用法非常的簡單,特別在標準查詢運算子中應用非常廣泛,提高了程式設計效率,且寫出的程式碼非常的簡潔。文中若有不足之處,還望海涵,博文寫作不易希望多多支援,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流! 

相關文章