iT邦幫忙

DAY 9
2

分享一些學習心得系列 第 9

LINQ自學筆記-打地基-Func委派、Action委派

上一篇提供 .Net 2.0 的泛型委派來簡化委派宣告端所需撰寫的程式碼,進入 .Net 3.5 後,微軟提供了 Func 委派和 Action 委派,再進一步簡化我們委派宣告端的工作。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
跟著上一篇「LINQ自學筆記-打地基-泛型委派」最後的範例,我們發現,幾乎所有的委派,都可以透過預先定義之不同參數數量的泛型委派,省去開發人員自行撰寫委派結構定義的程式,只是我們還是得自行撰寫一次這些泛型委派,而且也要予以命名。這件事在實務上會造成兩個情況:

  1. 兩個團隊會使用不同的名稱來定義這些泛型委派,如果兩邊程式要整合時,會有名稱上的困擾。
  2. 若是不使用匿名委派的情況下(例如:委派要串接多個方法),就必須明確知道泛型委派的名稱。

其實上述兩點的影響不算嚴重,但若能改進就更好啦,微軟很貼心的在 .Net 3.5 提出了 Func 委派和 Action 委派,它們有什麼特別?報告:一點都不特別,就只是把我們要自行撰寫的泛型委派先定義好,而且是放在 .Net Framework 中,方便使用,而且因為是官方提供的,所以大家使用時名稱都會固定就是 Func 和 Action,上述兩個問題就解決啦!

Func 委派有回傳值,Action 委派無回傳值,它們都位在 System 命名空間下。在 .Net 3.5 版本,兩者皆提供了無參數到 4 個參數的版本:

public delegate TResult Func<TResult>(); 
public delegate TResult Func<T, TResult>(T arg); 
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2, T3 arg3); 
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3); 
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); 
public delegate void Action(); 
public delegate void Action<T>(T obj); 
public delegate void Action<T1, T2>(T1 arg1, T2 arg2); 
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3); 
public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); 

有沒有發現上面這些泛型委派的定義,有一個不大一樣?是的,就是一個參數的 Action 委派,它的泛型方法參數名稱是「obj」,而不是「arg」,可知道為什麼嗎?

原因很簡單,因為 Action<in T> 這個泛型參數,是 .Net 2.0 就有提供,當時定義的泛型方法參數就是 obj,3.5 版本就繼續延用了。

再一個問題,有沒有覺得四個參數似乎不大夠,其實我也這麼覺得,但是 .Net 3.5 就只提供這樣了,幸好 .Net 4.0 就提供多達 0~ 16 個參數的 Func 和 Action 委派,實用!

Func委派、Action委派講完了耶!這篇文章這樣好像有點短,所以追加一個同事曾問過我的問題。我們有一些專案是 .Net 2.0 版本,後來升版到 3.5,他寫了類似下面的程式,試圖把自訂的委派轉型為 Func<int, int>,但失敗了:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text;

namespace CastDelegate 
{ 
    public delegate int MyDelegate(int num);//這是 .Net 2.0 時定義的委派 
    public class HouseMaster 
    { 
        //Cal 這是 .Net 3.5 同事新寫的方法,使用 Func 委派做為方法參數 
        public void Cal(Func<int, int> person, int Seed) 
        { 
            Console.WriteLine(person.Invoke(Seed)); 
        } 
    } 
    public class Caller 
    { 
        public static int DoSomething(int num) 
        { 
            return num * num; 
        }

        static void Main() 
        { 
            HouseMaster x = new HouseMaster(); 
            MyDelegate dgA = DoSomething; 
            Func<int, int> dgB = (Func<int, int>)dgA; 
            x.Cal(dgB, 9); 
        } 
    } 
} 
//編譯結果:錯誤    1    無法將型別 'CastDelegate.MyDelegate' 轉換為 'System.Func<int,int>' 

原因為何?

因為型別不一致啊,雖然方法參數都相同,但是請記得,他們是截然不同的型別,無法轉換也是理所當然。原因很清楚,但是找解法還是研究好一陣子,最終其實很簡單,只要下面這樣就行了:

static void Main() 
{ 
  HouseMaster x = new HouseMaster(); 
  MyDelegate dgA = DoSomething; 
  //Func<int, int> dgB = (Func<int, int>)dgA;

  Func<int, int> dgC = dgA.Invoke; //這行就是解法

  x.Cal(dgC, 9); 
}

下一篇,委派呼叫端將利用 Lambda 運算式,做進一步的簡化囉!下一篇也將是委派系列的最後一篇(呼…終於快搞定委派了……)


上一篇
LINQ自學筆記-打地基-泛型委派
下一篇
LINQ自學筆記-打地基-Lambda 運算式
系列文
分享一些學習心得30

尚未有邦友留言

立即登入留言