什麼是Lambda 表達式
Lambda表達式??
相信很多朋友一開始看到 => 跟我一樣的疑惑,我一開始聽到 Lambda 腦中浮現的想法是這個
來自我童年偉大的遊戲 Half-Life
好回歸正題!!所以究竟什麼是𝜆
它是C#3.0出現的語法
至今已經有數十年的時間了,而且大量的廣泛運用在LINQ查詢語句中。
可能剛接觸的人會有一個誤解,Lambda表達式並不是LINQ特有的,它們只是C#語言的一部分,只是在使用LINQ查詢時會經常使用到,如果一開始就跟大家解釋Lambda表達式的定義,可能會很抽象並且難以理解,讓我們直接從code開始理解它吧。
Let's Get Ready To Rumble!!!
開啟一個新專案
還是像之前我提到的,學習一個知識環境越簡單越好,而且這次的Sample Code 也很簡單,讓我們開啟一個主控台的專案。
我們從LINQ中最容易入門的方法Any來講解Lambda表達式,它會去檢查當前集合中所有的element,只要有任一個符合條件就會回傳布林值True,如果都不符合則會回傳 False
先創造出一組Array,我這邊是塞入費氏數列,有奇數跟偶數比較方便演示,數字可以隨便定義
我們來寫一個方法來檢查裡面這些整數是否有大於200
static bool IsAnyLargerThan200(int[] numberArray)
{
foreach (var number in numberArray)
{
if (number > 200)
{
return true;
}
}
return false;
}
這個方法相當的簡單,它只是會去iterates 整個集合的數字,如果其中有任一個數字大於200就會回傳True,沒有的話就會返回false,但他也很具體,他只能處理集合中是否有大於200的數字。
那假如說今天需求改變了,我們要檢查是否集合中是否有偶數呢?
static bool IsAnyEvenNumber(int[] numberArray)
{
foreach (var number in numberArray)
{
if (number % 2 == 0)
{
return true;
}
}
return false;
}
這個方法與我們上面寫的檢查是否有數字超過200的方法,重複度非常的高。
假如今天需求又改變了,我需要檢查集合中是否有質數,或是集合中是否有小於0的負數。
我們會需要創建越來越多幾乎一模一樣的方法,而且他們只會在if 判斷式這個部分有所不同而已。
這樣其實不是一個好現象,而且已經可以聞到了程式碼的壞味道(Bad Smell)
如果我們可有一個方法直接在if的判斷式這邊,然後他只回傳布林值那就很理想
讓我們來對我們的程式碼改造一下
static bool IsAnyLargerThan200(int[] numberArray)
{
foreach (var number in numberArray)
{
if (IsLargerThan200(number))
{
return true;
}
}
return false;
}
static bool IsLargerThan200(int number)
{
return number > 200;
}
static bool IsAnyEvenNumber(int[] numberArray)
{
foreach (var number in numberArray)
{
if (IsEven(number))
{
return true;
}
}
return false;
}
static bool IsEven(int number)
{
return number % 2 == 0;
}
經過我們的改良過後,我們目前兩個方法的差異性只在這邊了
如果我們能將IsLargerThan200、IsEven 這兩個方法合而為一
並且當作參數傳遞進來,似乎是更好的做法。
委派(Delegate)
在C#當中我們能否將method如同常用的string,int,bool 或其他的類型一樣當作參數一樣傳遞?
是的我們可以利用委派(Delegate),如果有再寫C/C++的朋友,委派類似函數指針的概念
使用委派我們可以將method作為參數傳遞,並且還能將它們賦值給 variables
static bool IsAny(int[] numberArray, Func<int, bool> predicate)
{
foreach (var number in numberArray)
{
if (predicate(number))
{
return true;
}
}
return false;
}
這裡我使用了C#中的泛型委派Func<>,在C#中有兩種泛型委派 Func<> 以及 Action<>
Func<> 是有回傳值的泛型委派 ,Action<>是沒有回傳值的泛型委派
這裡的第一個泛型參數是 該 method 要傳入的參數類型
第二個泛型參數是返回值的類型
我們可以使用Func<>來定義具有多個參數的method
類似這樣,我們只要記得最後一個參數都是返回的類型
在我們目前的例子中 predicate 這個參數是接收int 作為輸入;並返回 bool 的 method
剛好我們現在的方法 IsLargerThan200 以及 IsEven 都是這樣的method
都是接收int 作為輸入;並返回 bool
var numberArray = new int[] { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 };
Console.WriteLine($"集合是否中是否有大於200的數字: {IsAny(numberArray, IsLargerThan200)}");
Console.WriteLine($"集合是否中是否有偶數: {IsAny(numberArray, IsEven)}");
static bool IsAny(int[] numberArray, Func<int, bool> predicate)
{
foreach (var number in numberArray)
{
if (predicate(number))
{
return true;
}
}
return false;
}
static bool IsLargerThan200(int number)
{
return number > 200;
}
static bool IsEven(int number)
{
return number % 2 == 0;
}
這是目前我們現在的code
看起來不錯喔!我們的code現在是可以正常編譯的,我們在這邊已經將method當作一個參數傳入
我們來看一下是不是能正常運行
看起來不錯喔,我們的Array確實有大於200的數字、在裡面也有偶數。
What are Lambda Expressions
好的目前我們知道,在C#中我們可以將函數作為參數傳遞
那麼Lambda表達式是用來做什麼的呢?
Lambda表達式 用來建立匿名方法,這表示方法沒有具體名稱
我們現在有兩個具體的方法 IsLargerThan200、IsEven
但假如我們又有了新的需求是要檢查 是否有奇數、是否有質數、是否有小於0的數字
又要寫很多很具體的方法,有沒有可以解決這樣需求的方式呢??
沒錯就是Lambda表達式
現在讓我們把IsLargerThan200、IsEven 這兩個方法都刪掉
var numberArray = new int[] { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 };
Console.WriteLine($"集合是否中是否有大於200的數字: {IsAny(numberArray, number => number > 200)}");
Console.WriteLine($"集合是否中是否有偶數: {IsAny(numberArray, number => number % 2 == 0)}");
static bool IsAny(int[] numberArray, Func<int, bool> predicate)
{
foreach (var number in numberArray)
{
if (predicate(number))
{
return true;
}
}
return false;
}
現在完整的程式碼
讓我們來看一下發生了什麼
第二個參數我傳遞了Lambda表達式,它宣告了一個method,但是這個method是沒有名稱的,並且用一個特殊的語法宣告。
在這邊的這個箭頭=>叫做 Lambda Declaration Operator
箭頭左邊的內容是method parameters 也就是在目前的情況下我們有一個參數稱為 number。
如果我們想要一個Lambda表達式帶入多個參數的話我們可以這樣宣告
就像這樣,我們宣告了兩個參數 number 跟 point
在箭頭符號=>的右邊則是method的主體。
OK 我第一次看到這樣的語法的時候也很困惑
像是我們是在那裡定義參數的類型的?
我們並沒有定義我們的參數類型,參數的類型是compiler依據我們的context(上下文),自動推斷出來的。
它會知道你目前的參數是int型別
這又是為什麼呢?
我們在這邊定義的泛型委派,是定義輸入的參數要是int,因此compiler知道Lambda表達式必須是以int為參數的method。
就是這樣推斷出Lambda表達式需要帶入的參數型別,以及返回類型!
那又有一個疑問method 回傳的關鍵詞 return又在哪呢?
它只是將箭頭=>的右側部分當作method的結果然後return
有點類似這邊有個return的關鍵字。
這可能聽起來很抽象,而且不是那麼容易理解
再看看我們傳統的method,這樣聯想起來就更具體一些了
static bool IsLargerThan200(int number)
{
return number > 200;
}
如果我們的Lambda表達式 比較複雜,我們也可以使用一個{},然後在裡面定義我們的method主體,這樣看起來會更像傳統的方法。
對比一下其實這兩段code的意思是一樣的,只是上面使用{}在定義method主體的時候,我們還需要加入return關鍵字,如同C#普通的method一樣。
我們能不能將Lambda表達式 用一個variable來引用
是可以的,我們可以將Lambda表達式 賦值給Func<>類型的變數
那我們能否用var 隱含式宣告?
var 不能用於宣告匿名method。
正如我們所說的Lambda表達式是依造context推斷出來的
compiler 需要知道這個參數必須式int,使用var 隱含式宣告時compiler沒辦法判斷 number這個參數的類型。
我們通常會使用Lambda表達式來宣告不會重複使用,而且較為簡單的method
LINQ方法都使用Func<>當作參數的傳遞
這也是為什麼當我們在使用LINQ方法時會大量使用Lambda表達式的原因了。
再回來看看我們現在的code
var numberArray = new int[] { 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 };
Console.WriteLine($"集合是否中是否有大於200的數字: {IsAny(numberArray, number => number > 200)}");
Console.WriteLine($"集合是否中是否有偶數: {IsAny(numberArray, number => number % 2 == 0)}");
static bool IsAny(int[] numberArray, Func<int, bool> predicate)
{
foreach (var number in numberArray)
{
if (predicate(number))
{
return true;
}
}
return false;
}
我們的IsAny 方法現在只能接受int型別的集合,如果我們想將它改成LINQ中的Any方法,能將任意型別當作參數傳入,我們又該怎麼做呢?
static bool IsAny<T>(IEnumerable<T> collection, Func<T, bool> predicate)
{
foreach (var number in collection)
{
if (predicate(number))
{
return true;
}
}
return false;
}
我們可以用一個更抽象的類型IEnumerable<T>來讓它接收任意型別的集合
這裡的T代表泛型型別參數Generic Type Parameter
讓我們來驗證這一點
var animalList= new List<string> { "Cat", "Bird", "Fish", "Dog", "Elephant", "Lion", "Tiger", "Monkey", "Panda", "Rabbit" };
我們現在來檢查這個List裡面有沒有包含Dog的字串
完全沒問題,可以編譯也可以執行,也是我們預期的結果。
到這邊基本上我們已經自己實現了LINQ中的Any方法!
希望看到這邊的朋友能夠對Lambda有一個基礎的了解,它的確需要掌握一些C#的基礎知識才能夠理解,需要先了解委派、泛型、多型 等知識。
我覺得如果能用影片來操作將可以更好的表達這些觀點,有機會我會再整理一下錄製一部影片用
LINQPad來 Demo,也能補充更多不方便用圖文講解的地方。