[C#]使用委派(Delegate)與事件(Event) - HackMD

文章推薦指數: 80 %
投票人數:10人

[C#]使用委派(Delegate)與事件(Event) ###### tags: `C#` `Programming` :::spoiler Log - 2019/05/01 version.       Published LinkedwithGitHub Like4 Bookmark Subscribe #[C#]使用委派(Delegate)與事件(Event) ######tags:`C#``Programming` :::spoilerLog -2019/05/01version1 -2022/01/19更新錯誤範例與補充用法 ::: ##委派(Delegate) -委派是一種將方法(method)**安全封裝**的類別,描述需封裝函數的**參數型別**和**回傳型別** -可以使用`+`、`+=`、`-`、`-=`,將方法加入至委派的清單中 -引動(Invoke)委派時,委派會依序呼叫清單內的方法,並將傳入委派的參數傳入各方法中 -欲將委派物件的清單清空時,直接使委派指派為`null`即可 -`22/01/19`補充:可以使用`=`賦值運算子,使用的話跟賦值一樣會把前一個值蓋過去。

###基本使用 ```C#= /* *delegate(回傳型別)[委派類別](參數型別..) *宣告一個委派類別-ArithmeticExpr *傳入方法(method)的回傳型別為void,參數型別為兩個int */ publicdelegatevoidArithmeticExpr(inta,intb); //顯示出該方法的名稱與a+b的結果 publicstaticvoidAdd(inta,intb) { Console.WriteLine("Method:[Add]hasbeencalled.\nTheansweris:"+(a+b).ToString()+"\n"); } //顯示出該方法的名稱與a-b的結果 publicstaticvoidSub(inta,intb) { Console.WriteLine("Method:[Sub]hasbeencalled.\nTheansweris:"+(a-b).ToString()+"\n"); } //顯示出該方法的名稱與a*b的結果 publicstaticvoidMultiply(inta,intb) { Console.WriteLine("Method:[Multiply]hasbeencalled.\nTheansweris:"+(a*b).ToString()+"\n"); } staticvoidMain(string[]args) { //宣告三個委派 ArithmeticExprar1,ar2,ar3; /*******************Section1*******************/ ar1=Add;//起始將Add指派給ar1 ar1(10,7);//Invoke委派,呼叫Add並顯示出答案:17 ar1=Sub;//ar1指派為Sub ar1(10,7);//Invoke委派,呼叫Sub並顯示出答案:3 ar1.Invoke(10,7);//或是可以使用Invoke方法 /*******************Section2*******************/ ar2=Add;//起始將Add指派給ar2 ar2(3,41);//Invoke委派,呼叫Add並顯示出答案:44 ar2+=Sub;//將Sub方法加入ar2的方法清單中 //現在方法清單中有Add和Sub(順序為:Add->Sub) ar2(3,41);//Invoke委派,首先呼叫Add,答案為44;而後呼叫Sub,答案為-38 /*******************Section3*******************/ ar3=Multiply;//起始將Multiply指派給ar3 ar3+=Add;//將Add方法加入ar3的方法清單中 //現在方法清單中有Multiply和Add(順序為:Multiply->Add) ar3+=Sub;//將Sub方法加入ar3的方法清單中 //現在方法清單中有Multiply、Add和Sub(順序為:Multiply->Add->Sub) ar3(67,19);//Invoke委派,依序呼叫Multiply、Add和Sub ar3-=ar2;//將Add、Sub移出方法清單 ar3(67,19);//再次Invoke委派,這次只有ar3被呼叫 ar3=null;//清空整個方法清單 ar3(18,0);//ERROR==>方法清單已經被清空了,拋出例外 Console.ReadKey(); } ``` ###像物件一樣對待它... 這個方法目前不知道可以應用在哪裡,但可以這樣使用。

使用`System.MulticastDelegate.GetInvocationList()`可以獲得委派的方法清單,下列的Code可以將`ar2`的方法清單內方法印出來 ```C#= Delegate[]delArray=ar2.GetInvocationList(); for(inti=0;i方法清單已經被清空了,拋出例外 ... ``` 這個地方如果直接這樣使用會丟出例外,所以呼叫前會先進行nullcheck ```csharp= ar3=null;//清空整個方法清單 if(ar3==null)//SAFE! { ar3(18,0);//ERROR==>方法清單已經被清空了,拋出例外 } ``` C#6之後可以有更簡潔的寫法 ```csharp= ar3=null;//清空整個方法清單 ar3?.Invoke(18,0);//SAFE!呼叫invoke前,已經先檢查是否為null ``` 關於這個寫法`?.`稱作[Null條件運算子](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) ##事件(Event) -C#的事件(event)可以讓類別或物件在**發生相關事件**時,通知其他類別或物件。

-在定義事件(Event)之前,必須先定義委派(delegate)的型別,表示事件發生時,**呼叫其他類別或物件的函數**。

-**只能**使用`+=`和`-=`來註冊或取消註冊事件。

>**注意:當註冊該事件的類別或物件毀滅時,請務必許消註冊事件。

** **例子:** -在**A物件**發生某件事情時,同時改變**B物件**和**C物件**的狀態,例如:在**按下SPACE按鍵時**,**B物件**和**C物件**會同時回應**A物件**,直到回應五次。

>Main ```C#= classProgram { staticvoidMain(string[]args) { ObjectAobjectA=newObjectA("Kawaii",5); ObjectBobjectB=newObjectB(); ObjectCobjectC=newObjectC(); objectA.ObjAnounceEvent+=objectB.ObjBGreeting; objectA.ObjAnounceEvent+=objectC.ObjCGreeting; while(Console.ReadKey().Key==ConsoleKey.Spacebar) { objectA.OnObjAEventBeenCalled(); } } } ``` >ObjectA ```C#= classObjectA { publicstringName{get;set;} publicintCallCountThreshold{get;set;} privateint_callCount; publicdelegatevoidObjADelegate(ObjectAobjectA); publiceventObjADelegateObjAnounceEvent; publicObjectA(stringname,intcallCountThreshold) { Name=name; CallCountThreshold=callCountThreshold; _callCount=0; } publicvoidOnObjAEventBeenCalled() { _callCount+=1; if(_callCount>CallCountThreshold) { ObjAnounceEvent=null; Console.WriteLine("Cleartheinvocationlist."); Environment.Exit(0); } ObjAnounceEvent?.Invoke(this); } } ``` >ObjectB ```C#= classObjectB { publicvoidObjBGreeting(ObjectAobjectA) { Console.WriteLine("Hello,"+objectA.Name+".ThisisObjectB."); } } ``` >ObjectC ```C#= classObjectC { publicvoidObjCGreeting(ObjectAobjectA) { Console.WriteLine("Iloveyou,"+objectA.Name+".ThisisObjectC."); } } ``` ##.NET **.NET**中有定義**泛型**的委派類別 -**System.Action**:回傳值為void,可以給予0到16不等的參數值。

-**System.Func**:如果需要回傳值可以用Func,跟Action一樣,可以給予0到16不等的參數值,最後一個參數為**回傳值**的型別。

例如在上面介紹委派的例子可以這樣改寫: ```C#= //使用內建的Func進行改寫,最後一個泛型類別為回傳值 publicSystem.ActionArithmeticExpr; //顯示出該方法的名稱與a+b的結果 publicstaticvoidAdd(inta,intb) { Console.WriteLine("Method:[Add]hasbeencalled.\nTheansweris:"+(a+b).ToString()+"\n"); } //顯示出該方法的名稱與a-b的結果 publicstaticvoidSub(inta,intb) { Console.WriteLine("Method:[Sub]hasbeencalled.\nTheansweris:"+(a-b).ToString()+"\n"); } //顯示出該方法的名稱與a*b的結果 publicstaticvoidMultiply(inta,intb) { Console.WriteLine("Method:[Multiply]hasbeencalled.\nTheansweris:"+(a*b).ToString()+"\n"); } ``` -**System.EventHandler**:內部定義兩種EventHandler ```C#= publicdelegatevoidEventHandler(objectsender,EventArgse); publicdelegatevoidEventHandler(objectsender,TEventArgse); ``` **功能**:第一種的EventHandler是不帶*事件資料*的,第二種可以帶我們*自定義*的事件資料。

**首先**來談談參數列(args),兩種EventHandler都有名叫`sender`的參數,顧名思義,該參數指的是發起事件的物件或類別。

`e`則是需要傳遞的事件資料。

這樣寫的意義在於統一了事件委派的規則,讓程式碼易讀且好維護。

在傳遞事件的資料時,可以創建一個`class`繼承`EventArgs`,這樣就可把我們想傳遞的訊息往創建的`class`裏頭塞了。

**問題來了**,**如果沒有想傳遞的事件資料呢?**這時可以使用`System.EventArgs.Empty`,這樣已表示沒有需要傳遞的事件資料了。

接下來,我們依照上面給的情境題進行改寫: >Main ```C#= classProgram { staticvoidMain(string[]args) { ObjectAobjectA=newObjectA("Kawaii",5); ObjectBobjectB=newObjectB(); ObjectCobjectC=newObjectC(); objectA.ObjAnounceEvent+=objectB.ObjBGreeting; objectA.ObjAnounceEvent+=objectC.ObjCGreeting; while(Console.ReadKey().Key==ConsoleKey.Spacebar) { objectA.OnObjAEventBeenCalled(); } } } ``` >ObjectA ```C#= classObjectA { publicstringName{get;set;} publicintCallCountThreshold{get;set;} publicintCallCount; publicEventHandlerObjAnounceEvent; publicObjectA(stringname,intcallCountThreshold) { Name=name; CallCountThreshold=callCountThreshold; CallCount=0; } publicvoidOnObjAEventBeenCalled() { CallCount+=1; if(CallCount>CallCountThreshold) { ObjAnounceEvent=null; Console.WriteLine("Cleartheinvocationlist."); Environment.Exit(0); } ObjAnounceEvent?.Invoke(this,newObjectAEventArgs(Name)); } } ``` >ObjectB ```C#= classObjectB { publicvoidObjBGreeting(objectsender,ObjectAEventArgsargs) { Console.WriteLine("Hello,"+args.Name+".ThisisObjectB."); } } ``` >ObjectC ```C#= classObjectC { publicvoidObjCGreeting(objectsender,ObjectAEventArgsargs) { Console.WriteLine("Iloveyou,"+args.Name+".ThisisObjectC."); } } ``` >ObjectAEventArgs ```C#= classObjectAEventArgs:EventArgs { publicObjectAEventArgs(stringname) { Name=name; } publicstringName{get;set;} } ``` 結語 --- 2019/05/01 -.NET有定義了Action、Func和EventHandler,大多時間不需要定義自己的委派與事件 -單純使用委派可以做到和事件一樣的效果,兩個(關鍵字)差別在於事件只能使用`+=`、`-=`訂閱與取消訂閱事件,可以確保不能用`=`把事件清單蓋過去 2022/01/19 -紀錄了一些工作上的用法,C#提供了很方便的方法,實作出觀察者模式(ObserverPattern) ##參考資料 >*[Unity事件機制淺談(C#events,unityevents)](https://dev.twsiyuan.com/2017/03/c-sharp-event-in-unity.html) >*[delegate(C#參考)](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/delegate) >*[事件(C#程式設計手冊)](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/events/index) >*[ActionDelegate](https://docs.microsoft.com/zh-tw/dotnet/api/system.action-1?redirectedfrom=MSDN&view=netframework-4.7.2) >*[FuncDelegate](https://docs.microsoft.com/zh-tw/dotnet/api/system.func-2?view=netframework-4.7.2) >*[EventHandlerDelegate](https://docs.microsoft.com/zh-tw/dotnet/api/system.eventhandler?view=netframework-4.7.2) 4 × Signin Email Password Forgotpassword or Byclickingbelow,youagreetoourtermsofservice. SigninviaFacebook SigninviaTwitter SigninviaGitHub SigninviaDropbox SigninviaGoogle NewtoHackMD?Signup



請為這篇文章評分?