iT邦幫忙

DAY 6
2

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

LINQ自學筆記-打地基-具名委派

要用 LINQ ,一定要知道什麼是委派。要了解委派到應用委派,則一定要從基礎模式學起,才會融會貫通!本篇將說明 .Net 委派的最基本模式:具名委派。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
委派(deletate)其實也是類別,只是它是一種特殊的類別。
多特殊?委派類別在宣告的同時,就必須要定義好此委派類別中【唯一】的方法簽章(方法回傳值和方法參數的組合)。是的,最特殊的地方就是每個委派類別只有一個方法,不能自訂其他額外的成員,不像一般類別要多少成員屬性、方法、欄位,都可以自由定義。委派類別還有一個特性,它是屬於「密封」(sealed)類別,也就是無法被繼承。

委派的用途是什麼?我個人最喜歡的解釋是,委派可以讓我們把一個流程中某個部分之邏輯,留給呼叫者決定,例如:我建立一個訂單匯出Excel的功能,但是匯出那些欄位,由呼叫者決定。另外我也會這樣解釋:我只宣告有一件事情要做,但是這件事情誰做、怎麼做,由呼叫端和邏輯端處理。換言之,其實整個委派應該要切成宣告端、邏輯端、呼叫端三個面向來看:

宣告端:定義委派結構。
邏輯端:設定執行細節。
呼叫端:負責建立宣告和邏輯連接關係,並引動(Invoke)委派以執行邏輯端所設定之執行細節。

特別要注意一個限制:要和委派建立關係的方法,其方法簽章必須和委派宣告時的方法簽章相符,否則編譯會出錯。

PS. 這個限制有彈性,但正常情況下不會用到,有興趣的人可以閱讀 MSDN:http://msdn.microsoft.com/zh-tw/library/ms173174(v=vs.90).aspx

以下我設計一個情境,讓大家更容易了解委派的概念。
我是一間豪宅的新到任管家(女僕比較吸引人,但是我不想反串咩),主人交代我房子裡不可以有蟲出沒,看到蟲就火掉我。身為專業管家,我當然要先準備好蟲出沒的應變措施:當我發現房子裡有蟲出沒,我就要找除蟲高手來殺蟲,這樣就搞定啦。翻譯為程式碼如下:

//具名委派 
//宣告端 - 豪宅主人交代房子裡不能有蟲,不然就火掉我,所以我要先準備好蟲出沒的應變措施,也就是要除蟲: 
public delegate string 除蟲(string 蟲); 
public class 豪宅 { 
    public void 蟲出沒(除蟲 人){ 
        Console.WriteLine(人("蟑螂")); 
    }
}
//邏輯端 - 執行業務的人 
public class 除蟲高手 { 
    public string 噴藥(string 蟲)  { 
        return "噴殺蟲劑讓 " + 蟲 + " 掛點。\n"; 
    } 
}
//呼叫端 - 這是管家 
void Main()  { 
  豪宅 白宮 = new 豪宅(); 
  除蟲高手 高手 = new 除蟲高手(); 
  除蟲 除蟲的人 = new 除蟲(高手.噴藥); 
  白宮.蟲出沒(除蟲的人); 
}
//輸出:噴殺蟲劑讓 蟑螂 掛點。

透過情境來對照程式碼,應該就清楚多了吧。宣告端定義一個除蟲的委派,然後也定義了什麼時間點(蟲出沒)要使用這個委派,邏輯端則定義了除蟲的實際執行方法,但兩者並無關係,所以最後需要由呼叫端將兩邊串連起來,並在正確的時間點,也就是蟲出沒的時候,引動這個委派作業。

接下來繼續說故事囉,因為豪宅中窗明几淨,無蟲也無塵,相信主人一定非常滿意,但是主人外出遊玩回來,一定會肚子餓,所以我必須在主人肚子餓時叫人備餐,而且主人愛喝湯,所以除了主食一定要有湯品:

//具名委派 
//宣告端 - 幫主人備餐 
public delegate void 備餐(); 
public class 豪宅主人 { 
    public void 肚子餓(備餐 人){ 
        人(); 
    } 
} 
//邏輯端 - 執行業務的人 
public class 廚師{ 
    public void 烤牛排(){ 
        Console.WriteLine("牛排烤好了"); 
    } 
    public void 煮雞湯(){ 
        Console.WriteLine("雞湯煮好了"); 
    } 
    public void 炒飯(){ 
        Console.WriteLine("蛋炒飯完成"); 
    } 
} 
//呼叫端 - 這是管家 
void Main()  { 
  豪宅主人 主人 = new 豪宅主人(); 
  廚師 阿達師 = new 廚師(); 
  備餐 主食 = new 備餐(阿達師.烤牛排); 
  備餐 湯品 = new 備餐(阿達師.煮雞湯); 
  備餐 主食加湯品 = 主食 + 湯品; 
  主人.肚子餓(主食加湯品); 
} 
/* 輸出: 
雞湯煮好了 
牛排烤好了 
*/

這裡和前面有些不同囉,因為主人要吃主食和湯品,所以「備餐」委派在程式中被 new 了兩次,然後再用另一個「備餐」委派把它們串連起來。這個方式的專有名詞叫:多點傳送(multicasting),也就是委派一經叫用時,可以呼叫多個方法,只要使用加法或加法指派運算子 ('+' 或 '+=') 加入兩個委派即可。所以上述第三個委派也可以省掉改以主食委派用 「+=」運算子 把湯品委派串連起來:

void Main()  { 
  豪宅主人 主人 = new 豪宅主人(); 
  廚師 阿達師 = new 廚師(); 
  備餐 主食 = new 備餐(阿達師.烤牛排); 
  備餐 湯品 = new 備餐(阿達師.煮雞湯); 
  //備餐 主食加湯品 = 主食 + 湯品;  <--被註解囉 
  主食 += 湯品;  //改用主食委派 +=  串接湯品委派 
  主人.肚子餓(主食); 
}

那多點傳送委派時,委派的方法調用順序為何?答案是依加入的順序,依序引動。

那怎麼移除已經加入多點傳送委派的方法呢?答案是使用遞減或遞減指派運算子('-' 或 '-=')即可:

void Main()  { 
  豪宅主人 主人 = new 豪宅主人(); 
  廚師 阿達師 = new 廚師(); 
  備餐 主食 = new 備餐(阿達師.烤牛排); 
  備餐 湯品 = new 備餐(阿達師.煮雞湯); 
  備餐 飯= new 備餐(阿達師.炒飯); 
  //備餐 主食加湯品 = 主食 + 湯品;  <--被註解囉 
  主食 += 湯品;  //改用主食委派 +=  串接湯品委派 
  主食 += 飯; 
  主食 -= 湯品;  //用 –= 運算子把湯品移出多點傳送委派的方法清單中 
  主人.肚子餓(主食); 
}
/* 輸出: 
牛排烤好了
蛋炒飯完成 
*/

PS. 多點傳送委派是繼承自 MulticastDelegate,其為 System.Delegate 的子類別,所以我們不用特別宣告為 MulticastDelegate 即可使用。

以上是 .Net 1.0/1.1 時,使用委派的寫法,也是委派最基本的模式,後續 .Net 2.0 的匿名委派、.Net 3.5 的通用委派、Lambda 運算式,都是從這個基本模式出發、簡化而成,所以本篇文章請朋友一定要先了解,閱讀後續文章才不會很卡。


上一篇
LINQ自學筆記-打地基-泛型方法
下一篇
LINQ自學筆記-打地基-匿名委派
系列文
分享一些學習心得30

1 則留言

0
john80513
iT邦新手 5 級 ‧ 2017-09-14 17:40:55

舉的例子真是生動好理解!

我要留言

立即登入留言