C#委托及事件处理机制浅析
| ? | ?事件可以理解為某個對象所發(fā)出的消息,以通知特定動作(行為)的發(fā)生或狀態(tài)的改變。行為的發(fā)生可能是來自用戶交互,如鼠標點擊;也可能源自其它的程序邏輯。在這里,觸發(fā)事件的對象被稱為事件(消息)發(fā)出者(sender),捕獲和響應(yīng)事件的對象被稱作事件接收者。 |
????在事件(消息)通訊中,負責事件發(fā)起的類對象并不知道哪個對象或方法會接收和處理(handle)這一事件。這就需要一個中介者(類似指針處理的方式),在事件發(fā)起者與接收者之間建立關(guān)聯(lián)。在.NET Framework中,定義了一個特殊的類型(delegate),來提供類似C++的函數(shù)指針的功能。本文通過一個定義和使用簡單自定義事件的例子,對.NET的事件處理機制加以分析,以加深對事件處理原理的理解。
????如圖所示,使用自定義事件,需要完成以下步驟:
????1、聲明(定義)一個委托類(型),或使用.NET程序集提供的委托類(型);
????2、在一個類(事件定義和觸發(fā)類,即事件發(fā)起者sender)中聲明(定義)一個事件綁定到該委托,并定義一個用于觸發(fā)自定義事件的方法;
????3、在事件響應(yīng)類(當然發(fā)起和響應(yīng)者也可以是同一個類,不過一般不會這樣處理)中定義與委托類型匹配的事件處理方法;
????4、在主程序中訂閱事件(創(chuàng)建委托實例,在事件發(fā)起者與響應(yīng)者之間建立關(guān)聯(lián))。
????5、在主程序中觸發(fā)事件。
????如按鈕點擊事件,就是用戶在程序界面點擊按鈕控件時由按鈕對象發(fā)出的消息,我們可以在界面程序中定義按鈕點擊事件處理方法來響應(yīng)這一消息。這里就使用了委托處理機制。
????一、委托的定義和使用
????委托(委派)的聲明(定義)格式如下所示:
????public delegate void MyDelegateClass(string message);
????其中delegate為委托類型關(guān)鍵字,MyDelegateClass是我們所定義的委托類的名稱。委托類型類似C++的函數(shù)指針,而且是類型安全的函數(shù)指針,如同C++的回調(diào)函數(shù)(CALLBACK)。委托(委派)類型有一個簽名(或稱識別標志,signature),只有與簽名特征匹配的方法才可以通過委托類型進行委派。
????從上面的定義中,可以看出我們定義的MyDelegateClass類的簽名特征,即只要是輸入?yún)?shù)為string,返回類型為void的方法都可以通過MyDelegateClass類進行指派。有了這一行定義語句,不需要我們再干什么,.NET編譯環(huán)境就會自動為我們生成委托類MyDelegateClass,并允許我們通過類似MyDelegateClass delegateObj = new MyDelegateClass(對象名.方法名)的方式創(chuàng)建委托實例,添加與該實例關(guān)聯(lián)的方法引用。.NET是如何做的呢?
????實際上,.NET在編譯時,是根據(jù)我們的委托聲明語句,為我們創(chuàng)建繼承自System.MulticastDelegate(抽象類,其根類為System.Delegate)的委托類。Delegate類具有Target和Method兩個類似指針的(引用)屬性,分別指向所引用的對象及其方法的地址,這樣,我們在使用委托類實例時實際上就是在調(diào)用對應(yīng)的對象方法。而且,Delegate類可以引用多個對象方法,利用其“+=”操作符,通過類似delegateObj += new MyDelegateClass(對象名.方法名)的語句,可以為委托類對象實例delegateObj添加多個方法引用,這些方法引用被保存在委托類的委托列表中,在使用委托類實例時,這些方法都會被調(diào)用。
????如果需要,我們可以通過Delegate類的GetInvocationList()取出這些委托,并查看其Target和Method屬性,獲取所引用的方法名等信息。
????下面以一個簡單例子來演示一下委托類型的定義和使用。
????1、創(chuàng)建一個目標類極其方法,提供給委托類型使用。
????//TargetClass.cs
????using System;
????using System.Collections.Generic;
????using System.Text;
????namespace Delegatete_EventTest
????{
????????//也可以創(chuàng)建單獨的類文件
????????public class TargetClass
????????{
????????????public static void Method1(string message1)
????????????{
????????????????Console.WriteLine("調(diào)用了目標方法1,參數(shù):" + message1);
????????????}
????????????public void Method2(string message2)
????????????{
????????????????Console.WriteLine("調(diào)用了目標方法2,參數(shù):" + message2);
????????????}
????????}
????}
????2、在主程序中定義并使用委托類型。如圖所示為程序中定義的委托類(包括其基類)的類視圖:
????//DeleGateExample.cs
????using System;
????using System.Collections.Generic;
????using System.Text;
????namespace Delegatete_EventTest
????{
????????class DeleGateExample
????????{
????????????//定義委托類型
????????????public delegate void MyDelegateClass(string message);
????????????//主程序方法
????????????static void Main(string[] args)
????????????{
????????????????//Test1();
????????????????//Test2();
????????????????Test3();
?????????????}
?????????????//測試1(僅為委托實例指派了一個目標方法)
?????????????static void Test1()
?????????????{
?????????????????//定義委托實例,并指派(關(guān)聯(lián))目標方法(注意是目標類的靜態(tài)方法)
?????????????????MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
?????????????????//運行委托實例(調(diào)用目標方法)
?????????????????delegateObj("just a test");
?????????????????//顯示委托實例所關(guān)聯(lián)的目標類極其方法
?????????????????Console.WriteLine("目標對象及方法:" + delegateObj.Target + ","
???????????????????????+ delegateObj.Method);
?????????????}
?????????????//測試2(如果不通過+=操作符而指派第二個目標方法,會覆蓋掉第一個目標方法關(guān)聯(lián))
?????????????static void Test2()
?????????????{
?????????????????//定義委托類對象實例,并指派第一個目標方法(目標類靜態(tài)方法)
?????????????????MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
?????????????????//為委托實例指派第二個目標方法(目標類對象方法)
?????????????????TargetClass targetobj = new TargetClass();
?????????????????delegateObj = new MyDelegateClass(targetobj.Method2);
?????????????????//運行委托實例(調(diào)用目標方法)
?????????????????delegateObj("just a test");
?????????????????//顯示委托列表包含的目標方法個數(shù)
?????????????????Console.WriteLine("該委托實例的目標方法個數(shù):"
?????????????????????????+ delegateObj.GetInvocationList().Length);
?????????????????//顯示委托實例的目標類極其方法名稱
?????????????????Console.WriteLine("目標對象及方法:" + delegateObj.Target + ","
?????????????????????????+ delegateObj.Method);
?????????????}
?????????????//測試3(委托調(diào)用及委托列表顯示)
?????????????static void Test3()
?????????????{
?????????????????//定義委托對象實例,并關(guān)聯(lián)第一個目標方法(目標類的靜態(tài)方法)
?????????????????MyDelegateClass delegateObj = new MyDelegateClass(TargetClass.Method1);
?????????????????//使用+=操作符為委托實例添加第二個目標方法(目標類對象方法)
?????????????????TargetClass targetobj = new TargetClass();
?????????????????delegateObj += new MyDelegateClass(targetobj.Method2);
?????????????????//運行委托實例(調(diào)用目標方法)
?????????????????//delegateObj.Invoke("just a tets");
?????????????????delegateObj("just a tets");
?????????????????//調(diào)用委托列表顯示方法
?????????????????DisplayDeObjList(delegateObj);
?????????????}
?????????????//委托列表的顯示方法(逐一顯示委托列表所包含的目標類極其方法名稱)
??????????????static void DisplayDeObjList(MyDelegateClass delegateObj)
??????????????{
??????????????????//顯示委托列表包含的目標方法個數(shù)
??????????????????Console.WriteLine("該委托實例的目標方法列表中存在" +
?????????????????????????delegateObj.GetInvocationList().Length+"個目標方法,分別是:");
??????????????????//逐一顯示委托列表中所指派的目標類極其方法名稱
??????????????????for (int i = 0; i < delegateObj.GetInvocationList().Length; i++)
??????????????????{
??????????????????????MyDelegateClass deObj = (MyDelegateClass)delegateObj.GetInvocationList()[i];
??????????????????????Console.WriteLine("目標對象及方法:" + deObj.Target + "," + deObj.Method);
??????????????????}
??????????????}????????????????????????????????
??????????}???
????}
????二、自定義事件的定義與處理
????1、在事件發(fā)起者類中定義事件:
????//EventSenderClass.cs
????using System;
????using System.Collections.Generic;
????using System.Text;
????namespace Self_DefinedEvent
????{
????????//聲明一個委托類(定義為公共類型,以便外部代碼使用)
????????public delegate void MyEventDelegate(string aMessage);//參數(shù)為提示信息
????????class EventSenderClass
????????{
????????????//定義一個事件屬性
????????????public event MyEventDelegate selfEvent;
????????????//定義一個激發(fā)自定義事件的方法
????????????public void RaiseSelfDefinedEvent()
????????????{
????????????????//事件是否被訂閱(被實例化),如果未訂閱,MessageArrived就是null,不會引發(fā)事件
????????????????if (selfEvent != null)
????????????????????selfEvent("Self-Defined event is raised.");
????????????}
????????}
????}
????2、在事件接收與處理類中定義事件處理方法
????//EventHandlerClass.cs
????using System;
????using System.Collections.Generic;
????using System.Text;
????namespace Self_DefinedEvent
????{
????????public class EventHandlerClass
????????{
????????????//定義接收消息的公共屬性
????????????public string receivedMessage;
????????????//自定義事件的處理方法
????????????public void ReceiveAndDisplayMessage(string message)
????????????{
????????????????receivedMessage = "自定義事件被響應(yīng),事件消息為:" + message;
????????????}
????????}
????}
????3、本例基于窗口應(yīng)用,把窗口(Form)類作為自定義事件處理的主程序。在初始化窗口對象時執(zhí)行自定義事件的訂閱,即為自定義事件添加負責事件接收和處理的對象方法(語法與前面例子中添加委托實例的目標方法相同);在窗口類中添加了一個按鈕和一個標簽控件,并把自定義事件的觸發(fā)放在了按鈕點擊處理方法中。點擊按鈕,自定義事件被觸發(fā),并使用標簽控件輸出事件響應(yīng)信息。
????//Form1.cs
????using System;
????......
????using System.Windows.Forms;
????namespace Self_DefinedEvent
????{
????????public partial class Form1 : Form
????????{
????????????EventSenderClass myEventSender;
????????????EventHandlerClass myEventHandler;
???????
????????????public Form1()
????????????{
????????????????InitializeComponent();
????????????????myEventSender = new EventSenderClass();
????????????????myEventHandler = new EventHandlerClass();
????????????????//訂閱(實例化)自定義事件???????????
????????????????myEventSender.selfEvent +=
???????????????????????????????new MyEventDelegate(myEventHandler.ReceiveAndDisplayMessage);
????????????}
????????????//按鈕點擊處理方法
????????????private void button1_Click(object sender, EventArgs e)
????????????{
????????????????//觸發(fā)自定義事件
????????????????myEventSender.RaiseSelfDefinedEvent();
????????????????label1.Text = label1.Text + myEventHandler.receivedMessage;
????????????}
????????}
????}
????4、以按鈕為例,理解.NET的事件處理方式
????實際上,.NET的控件事件處理方式正是采用了前面所講的自定義事件的處理機制。以上例中的按鈕事件處理為例,打開Form1.Designer.cs,可以找到按鈕事件的訂閱語句:
????this.button1.Click += new System.EventHandler(this.button1_Click);
????解析一下這個語句,“Click”是System.Windows.Forms.Button按鈕類的事件屬性,button1_Click是處理按鈕事件的目標方法名,System.EventHandler則是.NET已定義好的用于事件處理的委托類型。這是.NET事件訂閱的典型語法。
????5、動態(tài)控件的定義和使用
????在實際項目中有時事先并不知道程序界面中需要哪些控件,需要幾個,這時就需要根據(jù)不同的條件動態(tài)生成不同的控件并使用。這里我們僅以一個簡單例子加以說明。
????在上面的Windows界面應(yīng)用程序中添加一個界面類Form2.cs:
????......
????namespace Self_DefinedEvent
????{
????????public partial class Form2 : Form
????????{
????????????public Form2()
????????????{
????????????????InitializeComponent();
????????????????Button but1 = new Button();
????????????????but1.Text = "動態(tài)按鈕";
????????????????but1.Click += new EventHandler(this.but1_Click);
????????????????this.Controls.Add(but1);
????????????}
????????????//動態(tài)按鈕處理方法
????????????private void but1_Click(object sender, EventArgs e)
????????????{
????????????????Label lb = new Label();
????????????????//設(shè)置標簽位置,實際應(yīng)用中要涉及到界面布局,如利用動態(tài)表格設(shè)置控件位置等。
????????????????lb.Location = new System.Drawing.Point(0, 30);
????????????????lb.Size = new System.Drawing.Size(200,10);
????????????????this.Controls.Add(lb);
????????????????lb.Text = "Button is clicked.";
????????????}
????????}
????}
????修改項目中Program.cs中的內(nèi)容,將加載Form1界面的語句改成加載Form2界面,試一下動態(tài)按鈕的使用:
????//Program.cs
????......
????Application.Run(new Form2());
????......
????三、用delegate實現(xiàn)回調(diào)函數(shù)(類似C++的CALLBACK)
????1、C++的回調(diào)函數(shù)實現(xiàn)原理
????C++的回調(diào)函數(shù)實現(xiàn)原理是子程序(子類)調(diào)用主程序(主類,注意這里的主類和子類為相互獨立的類,并不存在繼承關(guān)系)的函數(shù)(方法)。這里以WinCE的UDP通訊為例講解。其原理為為:UDP通訊程序包括一個對話框主類CUDPDemoDlg,和包含打開本地端口、發(fā)送數(shù)據(jù)、接收數(shù)據(jù)等方法的子類CUDP_CE。其中子類的接收數(shù)據(jù)方法運行在一個獨立的線程中,以循環(huán)的方式讀取遠端發(fā)送來的數(shù)據(jù),當接收到數(shù)據(jù)后,通過回調(diào)函數(shù)將接受的數(shù)據(jù)傳遞給主程序。具體實現(xiàn)過程為:
????(1)在子程序中定義回調(diào)函數(shù)類型,如:
?????typedef void (CALLBACK* ONUDPRECV)(void*,char* buf,DWORD dwBufLen,sockaddr* saRecvAddress);
?????其中的void*參數(shù)一般對應(yīng)主程序地址(主類對象指針)。
????(2)在子類中定義回調(diào)函數(shù)類型的實例,如:
?????public ONUDPRECV??m_OnUdpRecv;
?????注意訪問類型要設(shè)置為public,便于主程序訪問。可以在子類構(gòu)造函數(shù)中將回調(diào)函數(shù)類型實例初始化為null(m_OnUdpRecv = null)。
????(3)在主類中定義一個回調(diào)函數(shù)(注意函數(shù)的輸入和返回參數(shù)類型與子程序中定義的回調(diào)函數(shù)類型是一致的,這點與委托類型相似),如:
private static void CALLBACK OnUdpCERecv(void * pOwner,char* buf,DWORD dwBufLen,sockaddr * addr);
????(4)在主類(對話框)的“打開”連接按鈕方法中,設(shè)置子類對象的回調(diào)函數(shù)類型實例為上面定義的回調(diào)函數(shù),同時調(diào)用子類對象的打開連接函數(shù)(注意將主類對象引用--主程序指針傳遞給了子類):
?????m_CEUdp.m_OnUdpRecv = OnUdpCERecv;
?????DWORD nResult = m_CEUdp.Open(this,m_LocalPort,......,m_RemotePort);
????(5)在子類的open方法CUDP_CE::Open(void* pOwner,int localPort,LPCTSTR remoteHost,int remotePort)中啟動數(shù)據(jù)接收線程,運行數(shù)據(jù)接收方法。
?????//傳遞主類對象指針,m_pOwner為子類中定義的保存對象指針的屬性:
?????void * m_pOwner;
?????m_pOwner = pOwner;
?????//創(chuàng)建線程并運行數(shù)據(jù)接收方法,CUDP_CE::RecvThread(......)為子類的數(shù)據(jù)接收方法
?????AfxBeginThread(RecvThread,this);
????(6)在子類的數(shù)據(jù)接收方法中,當接收到數(shù)據(jù)時通過回調(diào)函數(shù)將數(shù)據(jù)回傳給主類對象:
????UINT CUDP_CE::RecvThread(LPVOID lparam)
????{
??????????CUDP_CE *pSocket = (CUDP_CE*)lparam;????
??????????......
??????????while (TRUE)
?????????{
???????????????......
???????????????//調(diào)用回調(diào)函數(shù)將數(shù)據(jù)發(fā)送出去
???????????????if (pSocket->m_OnUdpRecv)
???????????????{
???????????????????pSocket->m_OnUdpRecv(pSocket->m_pOwner,pSocket->m_recvBuf,......);
???????????????}
???????????????......
?????????}
?????????......
????}
????(7)在主類的回調(diào)函數(shù)中處理并顯示通過UDP連接從遠端接收到的數(shù)據(jù)。
????//UDP數(shù)據(jù)接收回調(diào)函數(shù)
????void CALLBACK CUDPDemoDlg::OnUdpCERecv(void * pOwner,char* buf,DWORD dwBufLen,......)
????{
?????????BYTE *pRecvBuf = NULL; //接收緩沖區(qū)
?????????//得到父對象指針
?????????CUDPDemoDlg* pThis = (CUDPDemoDlg*)pOwner;
?????????//將接收的緩沖區(qū)拷貝到pRecvBuf中
?????????pRecvBuf = new BYTE[dwBufLen];
?????????CopyMemory(pRecvBuf,buf,dwBufLen);
?????????//發(fā)送異步消息,表示收到串口數(shù)據(jù)。在主類中定義了WM_RECV_UDP_DATA自定義消息,及消息處理函
?????????//數(shù),消息處理函數(shù)負責將接收到的數(shù)據(jù)在文本控件中顯示,過程略......
?????????pThis->PostMessage(WM_RECV_UDP_DATA,WPARAM(pRecvBuf),dwBufLen);
????}
????2、在C#中用delegate實現(xiàn)回調(diào)函數(shù)
????在C#中只需把主程序(主類)的方法作為委托的目標方法,就可以很容易地實現(xiàn)類似CALLBACK的回調(diào)函數(shù),這里不再贅述。有興趣的讀者可以自行實現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的C#委托及事件处理机制浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 可执行文件的分析(gcc G
- 下一篇: c#事件,委托机制(转)