[.NET] 由委派演進到 Lambda Expression

有很多技術,如果不從頭講的話,很多人會不知道當初為什麼要這麼做,而且這麼做的好處又是什麼,它能解決哪些問題。委派和 Lambda 的關係就是一個極好的例子。

有很多技術,如果不從頭講的話,很多人會不知道當初為什麼要這麼做,而且這麼做的好處又是什麼,它能解決哪些問題。委派和 Lambda 的關係就是一個極好的例子。

委派 (delegate) 是一個封裝函式物件的一個機制,它就像 C++ 的函式指標,利用委派,我們可以在很多地方共用同一個函式邏輯,或是將同一個物件內的運算邏輯交給實作者來決定,它也是一個用來實作通知機制 (notification) 以及發行/訂閱模式 (publisher/subscriber pattern) 的最佳機制。

一開始在 .NET Framework 1.0, 1.1 時,委派必須要明確宣告,稱為具名委派 (named delegate),所有的 .NET Framework 事件都有具名委派存在,一個具名委派規定了函式的回傳值與參數規格,和介面的用法有點像,但和介面不同的是委派可以動態指定,而不需要另外以物件執行個體封裝它。例如我們有下列物件的宣告:

{
    public delegate void MyDelegateVoid();
    public delegate int MyDelegateFunc(int A, int B);

    public void Method1(MyDelegateVoid VoidFunc)
    {
        VoidFunc();
    }

    public int Method2(MyDelegateFunc Func, int A, int B)
    {
        return Func.Invoke(A, B);
    }
}

其中的 MyDelegateVoid 與 MyDelegateFunc 就是具名委派,明確的宣告了不需參數與回傳值的函式以及需要參數與回傳值的規格,它們都是 Delegate 物件,透過 Delegate 物件,我們還可以進一步決定當呼叫此委派時,接收到呼叫指令的函式有哪些 (這也就是事件的作法)。

所以我們能使用下面的方法來使用委派:

MyObject ob = new MyObject();

// named delegate
ob.Method1(new MyObject.MyDelegateVoid(VoidFunc));
Console.WriteLine("Func invoked, return: {0}, ", 
    ob.Method2(new MyObject.MyDelegateFunc(Func), 1, 2));

只是,如果在一個委派多的環境,以及每個委派使用次數極少 (只有一個函式或方法使用它一次) 時,若要開發人員都要用具名委派,其實會很麻煩的,尤其是具名委派都要有一個名稱,相信我,取名字對開發人員來說是件大事,尤其是又有 Coding Standard 時。所以 .NET Framework 2.0 才開發出了匿名委派 (anonymous delegate),它可以直接在要求委派時給予一個匿名的委派宣告,這樣就不再需要具名委派。例如下面的例子,功能與前面具名委派完全相同:

ob.Method1(delegate
{
    Console.WriteLine("VoidFunc invoked.");
});
Console.WriteLine("Func invoked, return: {0}, ", ob.Method2((delegate(int A, int B)
{
    return A + B;
}), 
1, 2));

匿名委派看起來好像解決了問題,但是實際上卻不然,開發人員還是要決定參數的規格,如前面所示的 delegate (int A, int B),那麼有沒有辦法連這個都不用寫,而讓開發人員能夠用更簡單的寫法呢?Lambda Expression 就因此誕生了,如下列程式碼:

ob.Method1(() => Console.WriteLine("VoidFunc invoked."));
Console.WriteLine("Func invoked, return: {0}, ", ob.Method2((A, B) => A + B, 1, 2));

語法己經相當簡潔了吧,但這樣還是不夠,因為這只解決了呼叫端的問題,但宣告端並沒有解決,它們還是需要用具名委派來實作,所以微軟在 .NET Framework 3.5 內,除了 Lambda Expression 外,又加上了通用委派 (General Delegates) 的機制,讓實作物件的開發人員能更輕鬆。例如:

{
    public delegate void MyDelegateVoid();
    public delegate int MyDelegateFunc(int A, int B);

    public void Method1(MyDelegateVoid VoidFunc)
    {
        VoidFunc();
    }

    public int Method2(MyDelegateFunc Func, int A, int B)
    {
        return Func.Invoke(A, B);
    }

    public void Method3(Action VoidFunc)
    {
        VoidFunc();
    }

    public int Method4(Func<int, int, int> Func, int A, int B)
    {
        return Func(A, B);
    }

其中的 Method1/Method2 是用具名委派,而 Method3/Method4 用的是通用委派,開發人員只需要定義好參數與回傳值類型,就能輕鬆的實作委派功能,並且在函數內和具名委派般叫用即可。

通用委派在 .NET Framework 3.5+ 有三種:

  • Action, Action<T>, Action<T1, T2>, … Action<T1, …, T16>,它用來宣告不需回傳值的委派。
  • Func<TResult>, Func<T, TResult>, Func<T1, T2, TResult>, … Func<T1, …, T16, TResult>,它用來宣告需回傳值的委派,T/T1/T2 等為參數,TResult 為回傳值參數。
  • EventHandler<T>,它用來宣告事件,T 為事件參數。

通用委派在 .NET Framework 3.5 被大量使用,因為沒有它的話,LINQ 就無法做出來。

 

Reference:

http://msdn.microsoft.com/en-us/library/0yw3tz5k(v=vs.80).aspx

http://msdn.microsoft.com/zh-tw/library/system.delegate(v=vs.80).aspx

http://msdn.microsoft.com/en-us/library/bb534960.aspx

http://msdn.microsoft.com/en-us/library/system.action