[C#] 委派 Delegate 與 Lambda

  • 9589
  • 0
  • C#
  • 2018-04-14

委派 Delegate

講的簡單一點 他就像一個轉接頭

你充 iphone 需要轉接頭 但轉接頭的兩端能插什麼線是固定的

你買了 Type-C 的線 就不可能插進 Micro USB 的孔

但也就是說 只要是插的進去的線 就都可以讓你充電

今天你有錢可以用原廠的線 明天比較窮就用夜市牌的

反正只要插的進轉接器 就是可以用的線

 

接下來我們把上面這個例子轉成程式碼

我用紅色框起來的兩個框框 指的就是轉接頭的兩端 

只要兩端一模一樣 就表示符合這個轉接頭的規定 表示這條線可以用

也就是說 今天我們轉接頭 = 委派 = Delegate 的規定是 => 輸入string 輸出 int

你可以寫 100 個符合這樣規定的函式 隨你今天心情決定你轉接頭另一邊要放哪一個

有錢時就用原廠線 沒錢時就用夜市牌

反正只要是輸入string 輸出 int 的 都可以被這個 轉接頭 = 委派 = Delegate 使用

 

有了這個概念 接下來我們就可以看程式碼了


namespace ConsoleApplication9
{
    /// <summary>
    /// //重點 => BindFunc, GetHeight, GetWeight 三者簽章需一致 => 都是輸入 string 傳回 int
    /// </summary>
    class Program
    {
        //我做了一個轉接頭 規定只能插入輸入 string 輸出 int 的線
        delegate int InStrOutInt(string name);

        static void Main(string[] args)
        {
            InStrOutInt a = new InStrOutInt(GetHeight);  //傳統寫法
            InStrOutInt b = GetWeight;                   //語法糖

            //也可以用lambda來做匿名委派 => 就是明明是一個函式卻沒有取名的意思 => 具名函式就像下面的 GetHeight, GetWeight
            InStrOutInt c = name =>
            {
                return -1;
            };

            //呼叫
            var resultA = a("Tom");  //其實是呼叫 GetHeight    但我透過轉接頭 a 來間接呼叫他
            var resultB = b("Tom");  //其實是呼叫 GetWeight    但我透過轉接頭 b 來間接呼叫他
            var resultC = c("Tom");  //其實是呼叫 一個匿名函式   但我透過轉接頭 c 來間接呼叫他
        }

        static int GetHeight(string name)
        {
            return 180;
        }

        static int GetWeight(string name)
        {
            return 70;
        }
    }
}

 

而 Lambda 到底是什麼... 大家可以在網路上找到很多文章仔細講解

或是你直接把他當作讓委派的程式碼變的更精簡的東西即可 如同大家使用 LINQ 一樣

 

但聰明如你馬上就會發現

如上面例子 那我就直接寫死 一個 call GetHeight()、另一個 call GetWeight() 就好

何必自找麻煩 又做了一個轉接器 ( 委派 ) 多繞一圈 讓程式碼多出這麼多行?

所以請考慮下面這個例子


namespace ConsoleApplication9
{
    class Program
    {
        //我做了一個轉接頭 規定只能插入輸入 string 輸出 int 的線
        delegate int InStrOutInt(string name);

        static void Main(string[] args)
        {
            InStrOutInt func = name =>
            {
                return -1;
            };

            var text = "體重"; //user輸入身高=>得180, 輸入體重=>得70, 其他=>得-1
            switch (text)
            {
                case "身高":
                    func = new InStrOutInt(GetHeight);
                    break;
                case "體重":
                    func = GetWeight;
                    break;
            }

            //會變的只有 user 輸入的值 = 他要查什麼 但取得輸出結果的程式碼永遠一樣 就是 func("Tom")
            var result = func("Tom");
        }

        static int GetHeight(string name)
        {
            return 180;
        }

        static int GetWeight(string name)
        {
            return 70;
        }
    }
}

 

那我們再來看一個簡單的例子 想必大家都寫過 Webform 程式吧

假設我只做了一個按鈕 一按就會輸出 '你好'

想必大家都知道程式碼長成怎樣

using System;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Response.Write("你好");
    }
}

 

但大家有沒有想過 為什麼他會知道我按下那個按鈕 他要來幫我 call 這個 Button1_Click 的函式?

要解答這個問題很簡單 我們直接把這個函式從 .cs 檔中刪掉 故意讓他壞掉

就會看到大家很熟悉的畫面

如果我們點開看完整內容的話會發現

+= 這個詞 也是一種掛載委派的方式 是他幫我們做了按下按鈕要去呼叫哪個事件的這件事

而想想這帶給我們什麼好處?

.cs檔中看起來更精簡了 我只要專注於開發按下按鈕時要發生的 '那件事' => 商業邏輯

但按哪個按鈕觸發事件的關係 我可能不用知道 別人會幫我負責 或是雖然我知道 但程式碼不用寫在這邊

這也就是寫死 與不寫死之間的差異 = 委派之所以有彈性的地方

 

接下來再稍微講解一下 Lambda 的寫法


namespace ConsoleApplication9
{
    class Program
    {
        delegate int In2StrOutInt(string name, string id);

        static void Main(string[] args)
        {
            //兩個參數輸入時 lambda 匿名委派這樣寫
            In2StrOutInt func2 = (name, id) =>
            {
                return -2;
            };
        }
    }
}

 

using System;

namespace ConsoleApplication9
{
    class Program
    {

        static void Main(string[] args)
        {
            //用 lambda 寫了一個沒有名子的函式 = 匿名函式
            //輸入 string ( name ),輸出 int,
            //並且把這個沒有名子的函式委派給 tmpFunc (泛型委派)
            Func<string, int> tmpFunc = name =>
            {
                return -3;
            };

            var result = tmpFunc("Tom");  //得-3
        }
    }
}

 

using System;

namespace ConsoleApplication9
{
    class Program
    {

        static void Main(string[] args)
        {
            //沒有輸入參數時這樣寫 => 只寫()
            Func<int> tmpFunc = () => -4;    //當函式主體只有一行時 連{} 還有return 都可以省略

            var result = tmpFunc();          //得-4
        }
    }
}

 

若你想知道背後的原理 請看

https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions