LINQ自學筆記-打地基-泛型委派
因為參加 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; } }
}
}
--------
沒什麼特別的~
不過是一些筆記而已