上一篇中C# 委派(Delegate) (一)介紹了Delegate類基本觀念、宣告方式與使用方式,本篇將照著C#中委派的發展史中,繼續介紹Anonymous Function、Lambda Expression。
- Anonymous Function
還記得上篇的範例中,我們每宣告一個委派(delegate)的變數(variable)就要寫一個跟委派相同簽章的具名方法對應是吧?
但是這個專用來給委派變數宣告用具名方法,具名不是顯得很多餘嗎,我們似乎不太需要知道這個方法叫什麼名字啊。
public delegate int MyDelegate(int x);
static void Main(string[] args)
{
//AddOne是專門寫來給委派變數使用的方法,這個具名方法只用一次,從此以後AddOne沒在被用過。
MyDelegate mydelegate = new MyDelegate(AddOne);
}
public static int AddOne(int number)
{
return ++number;
}
所以在C#2.0中貼心的微軟發展了一套專門給委派變數宣告專用的語法糖(Syntactic sugar)叫做匿名方法(Anonymous Function)。 public delegate int MyDelegate(int x);
static void Main(string[] args)
{
//不需要再寫一個AddOne方法
MyDelegate mydelegate = delegate (int number) { return number + 1; };
}
所以在C#2.0中只要如上宣告就完成了委派變數的宣告。匿名方法的變數宣告中,左式必須要使用明確的委派類別來宣告(這邊使用MyDelegate),不能使用var關鍵字,如果堅持要使用var關鍵字宣告,本文最後面會提及。而右式指派的方式為delegate (參數型態 參數名稱) { 方法內容 };,如果沒有回傳型態則不用return,這樣就完成了匿名方法的宣告。
- Lambda Expression
C#3.0中,隨著Linq被引進,微軟更進一步發展語法更簡潔,功能更強大的匿名方法,這種匿名方法就是著名的Lambda表達式(Lambda Expression),Lambda不只可以完全取代原本的匿名方法,還可以進一步的發展出強大的表達式樹(Expression Tree,表達式樹的應用在整個C#的學習上非常進階,在此先不提及)。Lambda表達式分為兩種
- 運算式Lambda(Expression Lambdas)
- 陳述式Lambda(Statement Lambdas)
public delegate int MyDelegate(int x);
public delegate int MyDelegate2(int x,int y);
public delegate void MyVoidDelegate(object o);
static void Main(string[] args)
{
MyDelegate mydelegate = x =>x + 1;
MyDelegate2 mydelegate2 = (x,y) => x + 1;
MyVoidDelegate myVoidDelegate = o => Console.WriteLine(o);
}
Lambda運算式的宣告如上,特性就是只能有一行的運算式,而且只有為一個參數的時候參數的括弧可以省略(例:mydelegate),依照委派變數的簽章,運算式Lambda會自動回傳將運算式主體回傳,所以不用加上return,x=>x+1;等效於一個return x+1;的方法。運算式Lambda不只可以用於委派變數的指派,也能用於Expression Tree變數的指派。2.陳述式Lambda(input-parameters) => { statement; }
不同於運算式Lambda的陳述式Lambda,必須在=>右邊的主體加上大括弧,一個陳述式Lambda可以擁有複數的陳述式,而且根據回傳簽章必須明確的做出回傳的陳述式。
public delegate int MyDelegate(int x);
public delegate int MyDelegate2(int x,int y);
public delegate void MyVoidDelegate(object o);
static void Main(string[] args)
{
MyDelegate mydelegate = x => { return x + 1; };
MyDelegate2 mydelegate2 = (x,y) => { return x + y + 1; };
MyVoidDelegate myVoidDelegate = o => { Console.WriteLine(o); };
}
陳述式Lambda只能用於委派變數的指派,不能用於Expression Tree變數的指派,一個運算式Lambda要化為一個陳述式Lambda非常簡單,只要加上大括弧在根據簽章決定要不要回傳即可,除此之外陳述式Lambda也能在參數的部分直接指明型態(int x)=>{return x+1;}。LambdaExpression的出現,更簡潔的語法完全的取代了Anonymous Function,官網中甚至建議在C#3.0以後只要使用Lambda Expression就好,也把Lambda Expression、Anonymous Function一起重新統稱叫做匿名方法Anonymous Function,而delegate (參數型態 參數名稱) { 方法內容 };的方法已經不需要在使用我們只要看得懂、知道觀念、知道這一段發展的
至此為止,委派的介紹已經快要結束,實際上現在的委派還是不太好用不太直覺,下一篇中將會介紹C#委派的完成體,Func<>、Action<>是如何配合Lambda成為一個C#中強大的功能與顯著的特色。
總結來講:
- 微軟為了委派(delegate)變數宣告的方便,在C#2.0中發展出Anonymous Function的語法。
- 隨後在C#3.0隨著Linq推出更簡潔的Lambda Expression完全取代Anonymous Function。
- 現在Lambda Expression、Anonymous Function一起重新統稱叫做匿名方法Anonymous Function。
- Lambda Expression分為運算式Lambda(Expression Lambdas),與陳述式Lambda(Statement Lambdas),兩者都能用於委派的指派但只有運算式Lambda可以用於表達式樹的指派(Expression Tree)。
進階 -- 為什麼不能使用var隱含類型宣告委派
如果今天我們的了一種,不行!不用關鍵字var來宣告一定會死的病。
委派執行個體變數其實也是可以使用var關鍵字來宣告的。
1.直接使用方法名稱直接指派給宣告委派執行個體變數
var del =ToString;
會發現錯誤訊息,方法群組不能指派給隱含類型變數。因為ToString代表的不只是一個方法,他可能代表著一個或數個方法多載(overload)集合的群組,曾做方法群組,就算右式給的是自訂義的方法,已經確定了沒有多載,編譯器(compiler)還是無法確定我們之後會不會用這個自訂義的方法名稱去擴充多載。
錯誤原因:編譯器不知道我們的委派最後會是方法群組中的哪個方法
2.使用Lambda來指派委派執行個體變數
經歷1.的錯誤後那我們避開使用方法群組來指派var宣告委派,直接使用Lambda表達來指派。
var del = x=>x+1
var del = x=>{return x+1;};
很明顯的可以看出,我們根本無法判斷del後面的委派到底是什麼類型。
錯誤原因:編譯器不知道我們參數類型,或是回傳型態。
3.明確給予參數類型與回傳型態的匿名方法來指派
到這我們就是不信邪,那我們給變數執行個體var直接指派時就告訴他型態怎麼樣?
var del = (int x)=>{return x+1;}
var del = delegate(int x){return x+1;}
結果還是錯誤,我們都已經明確告知型態了怎麼還是不行呢。最後這一個範例牽扯到的問題比較深跟編譯器運行的設計有關,到這邊的探討為止,我們可以發現前兩種情況是錯的而第三種情況似乎是可行的,但總地而言這三種都是類似的寫法,今天一種類似的寫法可能有時候可行有時不行,如果編譯器要針對這些情況去判讀哪些是可行的而那些是不行的,所花的代價遠遠遠遠遠大於為了讓我們少打幾個字(Ide還會幫我們做),這就是為什麼不建議使用var來宣告委派執行個體的原因。
所以當然爾,假如我們一開始告訴編譯器明確的委派簽章。
var del = (MyStringDelegate)Console.WriteLine;
var del2 = (MyDelegate)(x=> x+ 1);
var del3 = (MyDelegate)(x => { return x + 1; });
var del4 = (MyDelegate)(delegate(int x){ return x + 1; });
var del = (Action<string>)Console.WriteLine;
var del2 = (Func<int, int>)(x => x + 1);
var del3 = (Func<int, int>)(x => { return x + 1; });
var del4 = (Func<int, int>)(delegate (int x) { return x + 1; });
這時候,我們最終就能以var完成委派執行個體的宣告。各種var宣告委派執行個體變數一次Show。
小小心得
若有謬誤
歡迎底下留言指正
上一篇:C# 委派(Delegate) (一)
參考:
https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
留言
張貼留言