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