2018年5月30日 星期三

函式傳遞的進化論 (4) - lambda expression

前面講到了delegate(委派)的語法,不過使用上還是有些繁瑣。
除了要宣告委派類別外,同樣必須要先宣告與定義符合委派類別的方法才可以使用。

  1. public delegate int Calculator(int a, int b);
  2.  
  3. public class Operator
  4. {
  5. public static int Plus(int a, int b)
  6. {
  7. return a + b;
  8. }
  9. public static int Sub(int a, int b)
  10. {
  11. return a - b;
  12. }
  13. public static int Mul(int a, int b)
  14. {
  15. return a * b;
  16. }
  17. public static int Div(int a, int b)
  18. {
  19. return a / b;
  20. }
  21. }

  1. public static void Main()
  2. {
  3. var calc = new Calculator(Operator.Plus);
  4. Console.WriteLine(calc(3,5));
  5.  
  6. calc = new Calculator(Operator.Sub);
  7. Console.WriteLine(calc(3,5));
  8. }

如你所見,有時候我們只是需要很簡單的方法(僅僅1-2行)或是該方法僅僅只使用個1-2次,但是卻需要額外宣告方法以及包覆的類別(如Operator類別)。
那麼有沒有更簡便的寫法呢?
有,C#有提供匿名方法(Anonymous Method)的方式讓我們可以省下方法的宣告:

  1. public delegate int Calculator(int a, int b);
  2. public static void Main()
  3. {
  4. Calculator calc = delegate(int a, int b)
  5. {
  6. return a+b;
  7. };
  8. Console.WriteLine(calc(3,5));
  9.  
  10. }

在第4行時就是使用匿名方法的方式,直接定義出一個方法以及其主體(return a + b)。
如此在使用委派時就可以隨時定義出想使用的方法。


不過...這樣就結束了嗎? 還沒呢!! C#還提供更懶人的方式:Lambda Expression
  1. public delegate int Calculator(int a, int b);
  2. public static void Main()
  3. {
  4. Calculator calc = (int a, int b) => { return a+b; };
  5.  
  6. Console.WriteLine(calc(3,5));
  7. }
第4行的(int a, int b) => { return a+b; }; 即是lamdba expression。
左邊的 (int a, int b) 表示方法的輸入參數,
中間的 => 即是lamdba expression的關鍵語法。
右方的 { return a+b; } 則是方法的主體。
整體來說這跟匿名方法有些相似,同樣都沒有方法名稱、輸入與輸出都一模一樣,唯一的差別就是少了關鍵字delegate以及多了 => 符號。

Lambda expression可不只這樣。
首先我們可以知道此Lambda expression會指派給Calculator委派,該委派的方法簽章是輸入兩個int並且回傳一個int。
因此藉由型別推斷,編譯器可以知道Lambda expression的輸入與輸出的型別。
所以左方的(int a, int b)可以省略型別宣告:
  1. Calculator calc = (a, b) => { return a+b; };

其次,因為方法主體只有一行而已,而且也只有單一動作,所以程式碼區塊的大括號{}以及return關鍵字也可以省略,編譯器會知道a+b就是回傳值。
  1. Calculator calc = (a, b) => a+b;
如此,是不是比匿名方法簡潔許多呢?
唯一的缺點就是跟以往的語法相比相差比較大,初次接觸lambda expression的人會需要一段時間熟悉。


另外,如果方法的傳入參數只有一個的話,連左邊的小括號也可以省略。例如Linq的Where方法:
  1. var list = new List {1,2,3,4,5};
  2. var list2 = list.Where(x => x > 3);

2018年5月28日 星期一

函式傳遞的進化論 (3) - delegate

C#也是物件導向程式語言家族中的一員,因此同樣無法直接將方法傳遞給另一個方法。
C#同樣可以使用interface的技巧來包覆方法,不過這邊將介紹C#本身提供的方式:delegate(委派)
Delegate有一點像C的function pointer,其作用就是可以接收與傳遞方法。

首先使用關鍵字delegate來宣告一個delegate的類別
  1. public delegate int Comparator(int first, int second);
這邊宣告名為Comparator的delegate類別,Comparator將會接受一種"參數是2個int並回傳1個int"的這種方法。


接著宣告一個方法可以接收Comparator類別,此方法會調用指派給委派物件的方法(有點繞口,讓我們看接下來的程式碼)
  1. public void BubbleSort(int[] array, Comparator c)
  2. {
  3. for(var round=0; round < array.Length; round++)
  4. {
  5. for(var i=0; i<array.Length-1; i++)
  6. {
  7. if(c(array[i], array[i+1]) > 0)
  8. {
  9. var temp = array[i];
  10. array[i] = array[i+1];
  11. array[i+1] = temp;
  12. }
  13. }
  14. }
  15. }
在第7行時呼叫此Comparator所接收到的方法。


接下來我們要宣告符合Comparator的方法:
  1. public int SortByAsc(int first, int second)
  2. {
  3. if(first < second)
  4. {
  5. return -1;
  6. }
  7. else if(first > second)
  8. {
  9. return 1;
  10. }
  11. else
  12. {
  13. return 0;
  14. }
  15. }


然後在呼叫bubbleSort時將此方法傳遞給Comparator物件
  1. public static void Main()
  2. {
  3. var array = new [] {4,7,1,6,9};
  4. var sorter = new Sorter();
  5. sorter.BubbleSort(array, sorter.SortByAsc);
  6. foreach(var elem in array)
  7. {
  8. Console.WriteLine(elem);
  9. }
  10. }


整體完整的程式如下:
  1. using System.IO;
  2. using System;
  3.  
  4. public delegate int Comparator(int first, int second);
  5.  
  6. public class Sorter
  7. {
  8. public static void Main()
  9. {
  10. var array = new int[] {4,7,1,6,9};
  11. var sorter = new Sorter();
  12. sorter.BubbleSort(array, sorter.SortByAsc);
  13. foreach(var elem in array)
  14. {
  15. Console.WriteLine(elem);
  16. }
  17. }
  18. public void BubbleSort(int[] array, Comparator c)
  19. {
  20. for(var round=0; round < array.Length; round++)
  21. {
  22. for(var i=0; i<array.Length-1; i++)
  23. {
  24. if(c(array[i], array[i+1]) > 0)
  25. {
  26. var temp = array[i];
  27. array[i] = array[i+1];
  28. array[i+1] = temp;
  29. }
  30. }
  31. }
  32. }
  33.  
  34. public int SortByAsc(int first, int second)
  35. {
  36. if(first < second)
  37. {
  38. return -1;
  39. }
  40. else if(first > second)
  41. {
  42. return 1;
  43. }
  44. else
  45. {
  46. return 0;
  47. }
  48. }
  49. }

我們重新再看一次此程式。
首先先宣告一個委派類別Comparator(line 4),接著宣告符合此委派類別的方法SortByAsc(line 37)。
在第12行時將SortByAsc方法傳遞給BubbleSort的第二個引數Comparator c,
而BubbleSort會在第27行時呼叫c所接收到的方法,即sorter.SortByAsc方法