LINQ自學筆記-打地基-泛型委派

  • 3624
  • 0

LINQ自學筆記-打地基-泛型委派

Dotblogs 的標籤: , ,

因為參加 ITHome 的鐵人賽,所以整理了自己學習 LINQ 時的資料,變成自學筆記系列,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。

另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。

PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/

---------------本文開始-------------

我們在具名委派時有說過,委派可分為宣告端、邏輯端和呼叫端,然後在上一篇匿名委派時,我們看到在 .Net 2.0 如何透過匿名委派的方式,大幅精簡呼叫端所需要撰寫的程式碼,但是當時並沒有說宣告端是否有可以精簡的部分。本篇文章,我們將先就 .Net 2.0 的泛型委派來看怎麼精簡宣告端,下一篇會再說明這種泛型委派的模式,到了 .Net 3.5,出現 Func 委派、Action 委派的變化。

先回顧一下在匿名委派時簡化後的程式碼:



public delegate string 除蟲(string 蟲); 
public class 豪宅 { 
    public void 蟲出沒(除蟲 人){ 
        Console.WriteLine(人("蟑螂")); 
    }
} 
//呼叫端

void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  白宮.蟲出沒(delegate(string 蟲) { 
                return "噴殺蟲劑讓 " + 蟲 + " 掛點。\n";} 
              ); 
}

宣告端在委派的角色中,它做了兩件事:

1. 定義一個委派結構,包含兩個部分:類別名稱和委派方法簽章。在範例中,委派名稱叫「除蟲」,方法簽章則是傳入一個 string 參數,回傳 string。

2. 定義委派被使用的時候點,也就是那個方法會需要使用委派。在範例中,就是「蟲出沒」這個方法會要求一個名為「除蟲」的委派參數,並在方法的執行細節中要求委派執行工作(又稱之為引動(Invoke))。

但是實務中應用委派時,我們會發現一件事:開發人員宣告了很多委派,可是這些委派的方法簽章都雷同,有時候甚至一模一樣,所以其實這些委派定義應該可以共用,但是因為宣告委派要定義名稱,所以總不能把叫做「除蟲」的委派拿去給「種花」用,這樣未來維護系統時,很容易產生混淆:

 
public class 豪宅 { 
    public delegate string 除蟲(string 蟲); 
    public delegate string 種花(string 花); 
    public delegate string 掃廁所(Location 地點); 
    public delegate string 巡邏(Area 區域); 
    
    public void 蟲出沒(除蟲 人){ 
        Console.WriteLine(人("蟑螂")); 
    } 
    public void 整理花園(種花 人){ 
        Console.WriteLine(人("玫瑰")); 
    } 
    public void 維護廁所清潔(掃廁所 人){ 
        Location l = new Location(); 
        l.Name = "主臥房"; 
        Console.WriteLine(人(l)); 
    } 
    public void 維護住宅安全(巡邏 人){ 
        Area a = new Area(); 
        a.Name = "大門"; 
        Console.WriteLine(人(a)); 
    } 
} 
//呼叫端 - 管家派工 
void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  白宮.蟲出沒(delegate(string 蟲) { 
                return 蟲 + " 死光光。";} 
              ); 
  白宮.整理花園(delegate(string 花) { 
                return 花 + " 種好了。";} 
              ); 
  白宮.維護廁所清潔(delegate(Location 地點) { 
                        return 地點.Name + " 廁所亮晶晶。";} 
                    ); 
  白宮.維護住宅安全(delegate(Area 區域) { 
                        return 區域.Name + " 安全無虞。";} 
                    ); 
} 
public class Master{ 
    private string name; 
    public string Name {get {return name;} set {name = value;}} 
} 
public class Area { 
    private string name; 
    public string Name {get {return name;} set {name = value;}} 
} 
public class Location { 
    private string name; 
    public string Name {get {return name;} set {name = value;}} 
} 
/* 輸出:
蟑螂 死光光。 
玫瑰 種好了。 
主臥房 廁所亮晶晶。 
大門 安全無虞。
*/

所以我們思考如何共用委派定義:是不是可以比照匿名委派一樣,宣告時省略委派名稱,或者是以一個較為「抽象」的名稱來代表。因為委派會被當做方法的傳入參數,委派名稱就等於方法參數的型別名稱,不可能省略,所以匿名不大可能;那抽象名稱,該怎麼設定才恰當,又是一個大問題,因為還會牽扯到方法簽章的內容。於是,我們把關注的焦點拉到委派的方法簽章。

方法簽章就是方法的回傳值和傳入參數的型別及數量,而方法簽章影響到我們怎麼對這個方法所屬的委派命名,更進一步推論,其實把委派名稱就當做方法名稱,那整個委派不過就是一個方法,而我們在「泛型」時有講過泛型方法,如果我們讓方法的傳入參數都變成泛型,由呼叫端執行時才決定,也就是讓這個抽像概念的委派變成是泛型委派(generic delegate),如此這個委派的適用性就更高了,委派的名稱理所當然可以用很抽象的表達方式完成。所以範例中,我們便可把「除蟲」、「掃廁所」、「巡邏」合併為一個「日常勤務」的委派,這樣只要是豪宅的工作任務,只有一個工作重點,那都可以透過「日常勤務」泛型委派來執行:


    public delegate TResult 日常勤務<T, TResult>(T 工作重點); 
    
    public void 蟲出沒(日常勤務<string, string> 人){ 
        Console.WriteLine(人("蟑螂")); 
    } 
    public void 維護廁所清潔(日常勤務<Location, string> 人){ 
        Location l = new Location(); 
        l.Name = "主臥房"; 
        Console.WriteLine(人(l)); 
    } 
    public void 維護住宅安全(日常勤務<Area, string> 人){ 
        Area a = new Area(); 
        a.Name = "大門"; 
        Console.WriteLine(人(a)); 
    } 
} 
//呼叫端 - 管家派工 
void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  白宮.蟲出沒(delegate(string 蟲) { 
                return 蟲 + " 死光光。";} 
              ); 
  白宮.維護廁所清潔(delegate(Location 地點) { 
                        return 地點.Name + " 廁所亮晶晶。";} 
                    ); 
  白宮.維護住宅安全(delegate(Area 區域) { 
                        return 區域.Name + " 安全無虞。";} 
                    ); 
}

因為只有一個工作重點,日常勤務這個泛型委派都可以輕鬆處理,省去重覆定義委派的程式碼。但是問題來了,日常勤務只收一個參數,如果有一些勤務是需要兩個參數,該怎麼辦呢?套用前面的邏輯,我就再定義一個接收兩個參數的泛型委派即可。三個參數呢,定義三個參數的泛型委派,以此類推。但是不管要接收幾個參數,其實都是日常勤務啊,而且甚至非日常勤務的工作,因為是泛型委派,所以只要參數的數量匹配,就可以套用,那我們是不是可以把「日常勤務」這個名稱再更抽象化?甚至以此泛型委派的概念向外推展,我們會推論出一件事實:「只要是委派,其實差異就是委派的方法簽章而已」,也就是不同的是這三件事情:

1. 回傳的資料型別。

2. 委派方法參數型別。

3. 委派方法參數的數量。

而透過「泛型委派」,我們就可以輕鬆解決上述 1、2 兩點和型別有關的差異,就只剩第 3 點而已了。那第 3 點的解法,就是上述的,多定義幾個不同數量的泛型委派囉。

好,那這三個差異都解決了,剩下什麼?泛型委派的名稱,前面有提到,委派名稱就是方法參數的型別名稱,因此不可能省略,一定要定義,但是解決上述三點後,這些不同數量的泛型委派名稱,似乎也沒有意義了,因為任何需要委派的地方都可以用啊!最後,我們只好定義把這些委派,都命名叫「委派」:


public delegate TResult 委派<T1, T2, TResult>(T1 委派參數1, T2 委派參數2); 
public delegate TResult 委派<T1, T2, T3, TResult>(T1 委派參數1, T2 委派參數2, T3 委派參數3); 

至此,我們雖然沒有直接把委派宣告端須撰寫的程式精簡,但因為把這些委派變成是極度抽象、高度共用的泛型委派,所以我們可以事先把不同參數數量的泛型委派定義好,然後放到獨立的專案中,編譯成獨立的 DLL,其他專案開發時,只要加入這個 DLL 參考,匯入命名空間,便可省去撰寫委派命名和定義的程式碼。

PS. 為易於了解,下面的範例是 VS2005 的版本。

用 GenericDelegateStore.cs 類別存放泛型委派:


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

namespace DelegatePractice_Net20.GenericDelegateStore 
{ 
    public delegate TResult 委派<T, TResult>(T 委派參數); 
    public delegate TResult 委派<T1, T2, TResult>(T1 委派參數1, T2 委派參數2); 
    public delegate TResult 委派<T1, T2, T3, TResult>(T1 委派參數1, T2 委派參數2, T3 委派參數3); 
    public delegate TResult 委派<T1, T2, T3, T4, TResult>(T1 委派參數1, T2 委派參數2, T3 委派參數3, T4 委派參數4); 
} 

在要使用的專案中,加入 GenericDelegateStore 的參考,並匯入命名空間,就可以直接使用,不用再自行定義委派了:


using System.Collections.Generic; 
using System.Text; 
using DelegatePractice_Net20.GenericDelegateStore;

namespace DelegatePractice_Net20 
{ 
    //宣告端 
    public class 豪宅 
    { 
        public void 蟲出沒(委派<string, string> 人) 
        { 
            Console.WriteLine(人("蟑螂")); 
        } 
        public void 維護廁所清潔(委派<Location, string> 人) 
        { 
            Location l = new Location(); 
            l.Name = "主臥房"; 
            Console.WriteLine(人(l)); 
        } 
        public void 維護住宅安全(委派<Area, string> 人) 
        { 
            Area a = new Area(); 
            a.Name = "大門"; 
            Console.WriteLine(人(a)); 
        } 
    } 
    class Program 
    { 
        //呼叫端 - 管家派工 
        static void Main(string[] args) 
        { 
            豪宅 白宮 = new 豪宅(); 
            白宮.蟲出沒(delegate(string 蟲) 
            { 
                return 蟲 + " 死光光。"; 
            } 
                        ); 
            白宮.維護廁所清潔(delegate(Location 地點) 
            { 
                return 地點.Name + " 廁所亮晶晶。"; 
            } 
                              ); 
            白宮.維護住宅安全(delegate(Area 區域) 
            { 
                return 區域.Name + " 安全無虞。"; 
            } 
                              ); 
        } 
    }

    public class Master 
    { 
        private string name; 
        public string Name { get { return name; } set { name = value; } } 
    } 
    public class Area 
    { 
        private string name; 
        public string Name { get { return name; } set { name = value; } } 
    } 
    public class Location 
    { 
        private string name; 
        public string Name { get { return name; } set { name = value; } } 
    } 
} 

--------
沒什麼特別的~
不過是一些筆記而已