Try   HackMD

LOSoMan的委派事件(Delegate And Event)講解(C#為主)

tags: LOSo講解 C#

委派

在我的LOSoMan的委派講解筆記裡,已經清楚地介紹了委派的用法及用途。

事件的宣告

通常我們講到事件會先提到委派的原因是因為事件的參數型別是從委派宣告出來的,舉個例子:

public delegate void MyHandler(object sender, MyEventArgs arg); public event MyHandler MySomeWorks;

這裡可以明確的了解"MyHandler"是自創的委派型別,而"MySomeWorks"正是這個型別的事件。
其中"MyEventArgs"為一個繼承"EventArgs"的自訂類別,程式部分為:

public class MyEventArgs : EventArgs { public EAnimalType Animal = EAnimalType.none; public EMotionType Motion = EMotionType.none; }

發佈者(Publisher)、訂閱者(Subscriber)

這兩個方法(Method)在委派事件扮演重要的角色:

  1. 發佈者(Publisher): 引發事件的方法,在方法內會對事件本身進行呼叫。
  2. 訂閱者(Subscriber): 事件接收處理的方法,為被事件註冊的方法。

在本次演示的程式中,訂閱者(subscriber)方法負責變更UI介面的顯示:

private void MySubscriber(object sender, MyEventArgs arg) { if (arg.Animal != EAnimalType.none && arg.Motion != EMotionType.none) { rtbShow.AppendText(string.Format("{0}正在{1}\n", arg.Animal, arg.Motion)); } else if (arg.Animal != EAnimalType.none && arg.Motion == EMotionType.none) { rtbShow.AppendText(string.Format("{0}很寂寞\n", arg.Animal)); } else { rtbShow.AppendText("格式錯誤\n"); } }

而發佈者(publisher)則是單純提供使用,並且在發佈者方法內去呼叫(Invoke)執行事件所註冊的方法,所以在這的程式碼為:

private void MyPublisher(object sender, MyEventArgs arg) { if (MySomeWorks != null) { MySomeWorks.Invoke(sender,arg); } }

由此可見,publisher所做的就只是先判斷事件是否被註冊,若有被註冊則去執行事件所註冊的方法。

註冊事件與執行

當我們的事件(Event)如委派(Delegate)一樣就是個容器,若事件未被註冊,則其為空的(null)。所以我們一樣可以利用+=及-=的方式將訂閱者方法加入或是移出事件這個容器。
因此在我們要演示的程式碼中只需要註冊一次:

MySomeWorks += MySubscriber;

左側的"MySomeWorks"即為剛才宣告的事件,而右側則為訂閱者方法。意思就如同我們將一顆蘋果放入籃子裡。

而當我們要執行時,只需使用剛才的發佈者方法即可:

MyPublisher(sender,arg);

sener為object型別的參數;arg為MyEventArgs型別的參數

整體程式碼與演示

首先配置一個UI介面,如下:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

然後主程式如下:

public partial class Form1 : Form { public delegate void MyHandler(object sender, MyEventArgs arg); public event MyHandler MySomeWorks; public Form1() { InitializeComponent(); MySomeWorks += MySubscriber; Initcbx(); } private void Initcbx() { foreach (EAnimalType x in Enum.GetValues(typeof(EAnimalType))) { cbxAnimal.Items.Add(x.ToString()); } foreach (EMotionType y in Enum.GetValues(typeof(EMotionType))) { cbxMotion.Items.Add(y.ToString()); } cbxAnimal.Text = EAnimalType.none.ToString(); cbxMotion.Text = EMotionType.none.ToString(); } private void btnShow_Click(object sender, EventArgs e) { MyEventArgs arg = new MyEventArgs(); arg.Animal = (EAnimalType)Enum.Parse(typeof(EAnimalType),cbxAnimal.Text); arg.Motion = (EMotionType)Enum.Parse(typeof(EMotionType), cbxMotion.Text); MyPublisher(sender,arg); } private void MySubscriber(object sender, MyEventArgs arg) { if (arg.Animal != EAnimalType.none && arg.Motion != EMotionType.none) { rtbShow.AppendText(string.Format("{0}正在{1}\n", arg.Animal, arg.Motion)); } else if (arg.Animal != EAnimalType.none && arg.Motion == EMotionType.none) { rtbShow.AppendText(string.Format("{0}很寂寞\n", arg.Animal)); } else { rtbShow.AppendText("格式錯誤\n"); } } private void MyPublisher(object sender, MyEventArgs arg) { if (MySomeWorks != null) { MySomeWorks.Invoke(sender,arg); } } } public enum EAnimalType { none,,,, } public enum EMotionType { none,,,, } public class MyEventArgs : EventArgs { public EAnimalType Animal = EAnimalType.none; public EMotionType Motion = EMotionType.none; }

執行後,在Animal combobox 點選"狗";在Motion combobox 點選"吃",按下"show"按鈕,演示結果為:

事件使用時機

因為個人在剛接觸c#時,對於委派事件有些疑惑,對於使用時機十分不解。但是,經過兩三個月的薰陶,對於委派事件有進一步的認識與理解。

對於事件的使用時機,分析如下:

  1. 我們可以將一個事件註冊多個方法,而當我們要讓一個發佈者去觸發所註冊的所有方法時,就能加以使用。
  2. 當我們在跨層傳遞資料時,委派事件是個不錯的選擇。例如:我在A類別使用了B類別,意思就是我在A類別宣告了B型別的參數,那當我在B改變資料後想連動到A裡面的方法,這時就可以使用委派事件。

結論

委派事件是時常用到的跨層傳遞方式,在MVC架構裡的使用,則以Model資料改變後,想連動回UI(View)的顯示時,時常使用。個人最近在寫得依些小程式,也曾使用靜態事件去連動多個UI層級的刷新。總之,是個不可或缺的好用工具。