作為一個C++程序員,學習C#應該不是一件很困難的事情,因為C#的許多特性都是從C++“繼承”(不精確的說法)來的。但是C#作為一門全新的編程語言,它必然有自己的新特性,而這些C++中并不存在的新特性正是我們從C++轉向C#的過程中必須認真重新學習的東西。“事件(event)”是C#的一個比較簡單的新特性,我們今天就從“事件”開始,看一看C#的事件到底是怎么回事。
C#的事件和Windows窗口編程中提到的“事件”、“消息”、“事件驅動”等在概念上是很類似的。我們在一個窗口上移動鼠標指針,系統就會產生WM_MOUSEMOVE消息(在vb中就是激發mousemove事件),只要我們告訴系統一個函數指針,系統就會通過這個回調函數通知我們,這是Windows窗口編程中的“事件”。C#中的事件的外延更廣,任何一個對象都可以擁有事件,客戶可以“定制”該對象的事件,當該對象的內部狀態發生特定的改變時,就會通過定制事件時指定的函數代理(delegate)調用這個函數通知客戶。當客戶不在需要事件通知時,可以“撤消”對該事件的定制。
Now is the show time!模仿秀現在開始!
一、C#版
我們先來看一個C#中一個“事件”的最簡單例子:
在VS.NET中新建一個C#的ConsoleApplication,項目名稱為“eventtest”。為該項目新加一個類MyClass,對應的源文件為MyClass.cs代碼如下:
using System;
namespace eventtest
{
//定義EventHandler函數代理
public delegate void EventHandler();
/// <summary>
///
/// </summary>
public class MyClass
{
//構造函數
public MyClass(){}
//聲明一個事件
public eventEventHandler AEvent;
//激發事件
public voidFireEvent()
{
if(AEvent != null)
{
//直接把event當做函數調用
AEvent();
}
}
}
}
public event EventHandlerAEvent;就是給MyClass定義了一個事件(通過event關鍵字),其事件處理函數(通知函數)的原型由EventHandler函數代理(類似C++中的函數指針)指定。
FireEvent()成員函數用于激發該事件,如果客戶定制了該event,當本類對象的FireEvent()公開方法被調用時,客戶應該可以得到通知。在Main函數里寫如下代碼:
using System;
namespace eventtest
{
/// <summary>
/// Class1 的摘要說明。
/// </summary>
class Class1
{
/// <summary>
/// 應用程序的主入口點。
/// </summary>
[STAThread]
static void Main(string[] args)
{
MyClassObj = new MyClass();
Obj.AEvent+= new EventHandler(MyEventHandler);//定制事件
Obj.FireEvent();//這行將導致MyEventHandler被調用
Obj.AEvent-= new EventHandler(MyEventHandler);//撤消事件
Obj.FireEvent();//這里將不會引發事件
Console.WriteLine("結束!");
Console.ReadLine();
}
//事件處理函數
public static voidMyEventHandler()
{
Console.WriteLine("Thisis a event!");
}
}
}
首先寫一個具有適當形式的事件處理(通知)函數MyEventHandler,然后通過Obj.AEvent+= new EventHandler(MyEventHandler)來定制事件。通過“-=”來撤消事件定制。
運行程序我們可以發現,當客戶(Class1)定制了Obj的AEvent事件后,在Obj的FireEvent()成員函數被調用時,客戶可以在MyEventHandler函數中得到通知(在這里只是簡單地輸出一個文本)。而當客戶撤消該事件的定制后,就不會再得到該事件通知。
二、C++版
下面我們在C++中模擬該機制:
由于C++不支持event關鍵字,我們就必須自己寫代碼。在這里我通過模板類的手段來實現,因為該手段實現的效果和C#比較類似。
在VC6中新建一個win32console app,命名為“cppevent“。新建一個.h頭文件,命名為“event.h”,代碼如下:
//event.h
template <typename Handler>
class event
{
private:
Handler m_Handler;
protected:
//模擬C# event 的add/remove訪問器
//如果要重新實現add/remove請在派生類中重寫這兩個函數
virtual void add(const Handler value){m_Handler = value;};
virtual void remove(const Handler value){if(value == m_Handler)m_Handler = NULL;};
public:
//構造函數
event():m_Handler(NULL){}
//+= 操作符
event& operator += (const Handler value)
{
add(value);
return *this;
}
//-=操作符
event& operator -= (const Handler value)
{
remove(value);
return *this;
}
//PFN_EVENT_HANDLE 操作符
operator Handler()
{
return m_Handler;
}
};
為了能夠在定義是指定事件處理函數的原型,我使用了template,為了能和C#一樣用+=和-=來定制和撤消事件,我重載了這兩個操作符(C#不支持操作符重載),為了能像C#一樣直接把event當做函數調用,我有重載了Handler自定義轉換操作符,可惜的是,這一點模擬得不是很像,在調用時還必須來一次強制轉換才可以:(,具體參看后面的代碼:
C++版的MyClass如下:
//MyClass.h
#include "event.h"
//定義EventHandler的函數指針類型
typedef void(*EventHandler)();
class MyClass
{
public:
//構造函數
MyClass(){};
//聲明一個事件
event<EventHandler> AEvent;
//激發事件
void FireEvent()
{
if(AEvent != NULL)
{
//C++中必須用EventHandler進行強制類型轉換
((EventHandler)AEvent)();
};
}
};
和C#版的MyClass比較一下你就會發現代碼非常接近,當然,C#是在語言級直接支持event關鍵字的,而C++不支持,用模板類代替,所以聲明事件的代碼有些不一樣。還有就是FireEvent()中C++不能把event對象直接當做函數來調用,多了強制類型轉換。
C++版的客戶代碼如下:
//cppevent.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "MyClass.h"
//向前聲明
void MyEventHandler();
int main(int argc, char* argv[])
{
MyClass Obj;
Obj.AEvent += MyEventHandler;//定制事件
Obj.FireEvent();//這行將導致MyEventHandler被調用
Obj.AEvent -= MyEventHandler;//撤消事件
Obj.FireEvent();//這個將不會引發事件
printf("結束!\n");
char n;
scanf("%c",&n);
return 0;
}
void MyEventHandler()
{
printf("Thisis a event!\n");
}
我們可以看到,可C#版的客戶代碼相比,核心部分是非常接近的,我們已經可以和C#一樣用“+=”和“-=”來定制事件和撤消事件定制,并在Obj的FireEvent()被調用時收到事件通知,輸出文本。
鑒于篇幅的原因,我們沒有仔細比較兩個版本的event的add和remove訪問器/成員函數,其實二者也是非常類似的,你可以自己試試。C++版的event的add和remove均為virtual的,你可以從event類繼承出來一個MyEvent類,然后重新實現這兩個函數,就可以定制自己的add和remove了。這和C#的add/remove訪問器的也是非常相像的。
三、總結
通過這場“模仿show”我們可以從更深的層次理解C#的event機制,更重要的是我們用自己所熟悉的東西(C++,模板類)來模仿并解釋了我們目前還不太熟悉的東西(C#,event)。
其實,C#的delegate就是C++的函數指針,C# event的核心機制就是C++中的模板(定義event時表現出來)和運算符重載(+=、-=和直接把event當做函數調用)的結合體。C#把C++中容易出錯的部分用“新特性”封裝了起來,把這部分工作從programmer身上轉移到了compiler身上,讓我們把更多的精力集中到業務邏輯的處理上。