2016年7月24日 星期日

C# Delegate and Event 委派和事件(一)

最近因為新工作的關係,經常用到事件,因為之前工作比較少用到,有用到也沒深入了解它的運作機制。
所以最近一直上網google看了許多事件的介紹,又發現事件好像又跟委派脫不了關係,所以順便也瞭解了一下什麼是委派

上網查了很多介紹事件和委派的介紹跟教學,還是看得霧煞煞@_@,不過最後還是花了一段時間把自己了解到的東西做個紀錄



我們現在有兩個方法一個是board佈告欄用於展示某些訊息,另一個則是PostMessage負責把輸入文字貼在佈告欄
public void Board (string message){
    PostMessage(message);
}

public void PostMessage(string message){
    Console.WriteLine (message);
}

現在假設這個board要根據不同語言來轉換成對應的語言,我們則可能會這樣改寫我們原本的程式,使用判斷式去判斷
public void Board (string message, Language lang){
         switch (lang) {
         case Language.Chinese:
             PostChineseMessage (message);
             break;
         case Language.English:
             PostEnglishMessage (message);
             break;
         }
}
public void PostChineseMessage(string message){
       //TODO Translate to Chinese
       Console.WriteLine(message + "(中文)");
}

public void PostEnglishMessage(string message){
       //TODO Translate to English
       Console.WriteLine (message + "(English)");
}
很明顯的這樣的做法,擴展性跟維護性很差,每當我們新增一種新語言時我們必須反覆新增跟修改到很多程式碼。

這時候就是用到委派的時候,可以把委派想像成方法的Type,讓我們可以把方法當成參數

public delegate void PostDelegate(string message);

class MainClass
{
 public static void Main(string[] args)
 {
     //  把方法當參數傳入
     Board("你好", PostChineseMessage);
     Board("hello", PostEnglishMessage);
     //  事實上我們可以直接使用委派呼叫方法
     PostDelegate d1;
     d1 = PostChineseMessage;
     d1("你好");
 }

 public static void Board(string message, PostDelegate postMessage)
 {
     //  在這可以加上一些postMessage共用的程式碼 
     message += "!";     //  在所有message後面加上驚嘆號
     postMessage(message);
 }

 public static void PostChineseMessage(string message)
 {
     //TODO Translate to Chinese
     Console.WriteLine(message + "(中文)");
 }

 public static void PostEnglishMessage(string message)
 {
     //TODO Translate to English
     Console.WriteLine (message + "(English)");
 }
}
我們上面宣告一個委派PostDelegate,這樣就可以把與PostDelegate簽名相符的方法當成參數傳入,減少程式中大量的判斷式,在Board()中還可以寫一些PostMessage() 共用的程式。
(Note:委派是一種類別)

委派與一般型別不同,可以將多個方法綁定到一個委派上,當呼叫委派時會依序執行。
public static void Main(string[] args)
{
    PostDelegate d;
    d = PostChineseMessage;
    d += PostEnglishMessage;
    Board("Hello", d);
}
Output:
Hello!(中文)
Hello!(English)

接下來介紹事件,在理解事件前,我們先修改寫一下程式碼

class MainClass
{
 public static void Main(string[] args)
 {
  PostManager pm = new PostManager();
  //  pm.Post = PostChineseMessage;   會編譯錯誤
  //  pm.Post("Hello");               不行直接呼叫事件! Post為private
  pm.Post += PostChineseMessage;
  pm.Post += PostEnglishMessage;
  pm.Board("Hello");
 }

 public static void PostChineseMessage(string message)
 {
  //TODO Translate to Chinese
  Console.WriteLine(message + "(中文)");
 }

 public static void PostEnglishMessage(string message)
 {
  //TODO Translate to English
  Console.WriteLine(message + "(English)");
 }
}

class PostManager
{
    public delegate void PostDelegate(string message);
    public event PostEventHandler Post;
    public void Board(string message)
    {
        // 在這可以加上一些postMessage共用的程式碼
        message += "!";
        if (Post != null)
          Post(message);
    }
}

上面把程式碼改成比較符合實際狀況,把Board()抽出單獨放在一個Class,在這個Class中宣告事件,提供給其它Class註冊。
看完以上程式碼,大概就可以大致了解到事件,簡單來說事件是為了物件導向的封裝,event封裝了委派,不管事件宣告什麼,他最終會被compiler成private,我們無法像之前一樣可以直接調用委派 ,事件會有兩個public 的方法可以使用add跟remove分別對應到"+="和"-=",無法直接使用 "=" 。
(Note:感覺有點像用PostDelegate這個類型宣告了一個Event成員)

.Net Framework 中的委派與事件
接下來我們把程式碼改成符合.Net Framework 的規範,使程式碼有更大的彈性
1. 委派類型的變數名稱以EventHandler結尾
public delegate void PostEventHandler (string message);

2. 委派類型的定義:
    1. 傳回值為 void
    2. 兩個參數 :
object類型(代表Subject本身,以上例就是PostManager)
EventArgs類型或繼承EventArgs類型
public delegate void PostEventHandler (object sender, EvenArgs e);

3. 事件變數名稱為委派名稱去掉EventHandler
public event PostEventHandler Post;

4.繼承EventArgs的類別名稱以EventArgs結尾
  這裡的EventArgs是包含Observer所感興趣的資料,以上為例
public class PostEventArgs : EventArgs{
 public readonly string message;
 public PostEventArgs(string message){
  this.message = message;
 }
}

我學東西是屬於食物派 實務派的,我學一個東西一定要知道為什麼要用,要怎麼用,用到哪裡,用它的理由好處在哪,我才學得下去。
我不知道這種學東西的態度是對還是錯,但我覺得人的一生時間有限,腦容量也有限,花點時間做些會對自己有幫助的事吧!
不然學了個東西不知道他要幹嘛可以改善什麼我就算學會過兩天就忘。

1 則留言: