[翻譯]簡單談談事件與委托
原文地址:http://www.codeproject.com/csharp/events.asp
源代碼下載:/Files/edgar-sun/events_src.zip
演示文件下載:/Files/edgar-sun/events_demo.zip
作者:Maysam Mahfouzi
原文發布日期:2003/8/16
原文更新日期:2005/5/14
內容
介紹
什么是委托?
理解事件
event關鍵字
結尾
介紹
當我設法學習事件與委托時,我閱讀了很多文章去完全地理解它們并使用它們,現在我想把我學到的展現在這里,其中有很多知識你也需要學習。
什么是委托?
委托和事件是緊緊聯系在一起的。委托是函數(方法)指針,更確切地說,委托保持方法的引用。
委托是一個類。當你創建它的實例的時候,你傳遞將被委托調用的方法名(做為委托構造器的參數)。
每個委托都有一個特征。例如:
是一個委托聲明。我之所以說它有一個特征,是因為它都返回一個int類型的值并帶有兩個參數,類型分別為
string和bool。
我說過,當你實例化委托時,你傳遞將被委托調用的方法名做為它的構造器參數。重要的一點是只有與委托具有相同特征的方法才能做為其參數。
看看下面的方法:
}你能把這個方法傳遞給SomeDelegate的構造器做參數,因為它們有相似的特征。
既然你已經知道怎么使用委托,下面讓我們來理解事件……
理解事件
一個按鈕是一個類,當你點擊它的時候,click事件被觸發。
一個計時器是一個類,每毫秒觸發一個tick事件。
想要知道發生什么了?讓我們通過一個例子去學習:
這是一個假定:我們有一個類Counter。這個類有一個CountTo(int counTo,int reachableNum)方法,從0到countTo計數,并且只要計數到reachableNum這個數時會觸發一個NumberReached事件。
我們的類有一個事件:NumberReached。事件是委托的變量。我的意思是,如果你聲明一個事件,同時也聲明某一類型的委托,并且要把event關鍵字放在聲明前面,看起來應該是這樣的:
在上面的聲明中,NumberReachedEventHandler只是一個委托。也許它更應該叫NumberReachedDelegate,但是注意到微軟并沒叫MouseDelegate或PaintDelegate,而是叫MouseEventHandler或PaintEventHandler。把它命名為NumberReachedEventHandler而不是NumberReachedDelegate,這只是一個慣例,了解?很好!
你了解了我們聲明事件前,需要先定義相應的委托(事件處理者)。它看起來可能是這樣的:
NumberReachedEventArgs e);
如你所見,我們委托的名字是:NumberReachedEventHandler,它的特征是返回一個void值,兩個參數類型分別為object和NumberReachedEventArgs。如果你在某處實例化這個委托時,你所傳遞的方法必須和它具有一樣的特征。
在你的代碼中,你使用過MouseEventArgs或PaintEventArgs去確定鼠標的位置,它在向哪移動,或某個物體的圖形屬性觸發Paint事件么?實際上,在從EventArgs類繼承的類中我們提供給使用者我們的數據。例如,在我們的例子中,我們提供那個可達的數。下面是這個類的聲明:
{
private int _reached;
public NumberReachedEventArgs(int num)
{
this._reached = num;
}
public int ReachedNumber
{
get
{
return _reached;
}
}
}
如果不需要提供給使用者任何信息,我們可以直接使用EventArgs類。
現在,所有的事都已經準備好了,下面讓我們來看一下Counter類的內部實現:
{
public delegate void NumberReachedEventHandler(object sender,
NumberReachedEventArgs e);
/// <summary>
/// Summary description for Counter.
/// </summary>
public class Counter
{
public event NumberReachedEventHandler NumberReached;
public Counter()
{
//
// TODO: Add constructor logic here
//
}
public void CountTo(int countTo, int reachableNum)
{
if(countTo < reachableNum)
throw new ArgumentException(
"reachableNum should be less than countTo");
for(int ctr=0;ctr<=countTo;ctr++)
{
if(ctr == reachableNum)
{
NumberReachedEventArgs e = new NumberReachedEventArgs(
reachableNum);
OnNumberReached(e);
return;//don't count any more
}
}
}
protected virtual void OnNumberReached(NumberReachedEventArgs e)
{
if(NumberReached != null)
{
NumberReached(this, e);//Raise the event
}
}
}
在上面的代碼中,如果到達預期的數時就觸發一個事件。在這里我們需要考慮很多事情:
1、觸發一個事件是通過調用我們的事件(NumberReachedEventHandler的一個實例)完成的。
這樣,所有已注冊的方法都將被調用。
2、我們給已注冊的方法數據通過以下代碼:
3、一個問題:我們為什么通過OnNumberReached(NumberReachedEventArgs e)方法間接的調用NumberReached(this,e)事件?為什么我們不用下面的代碼:
{
NumberReachedEventArgs e = new NumberReachedEventArgs(reachableNum);
//OnNumberReached(e);
if(NumberReached != null)
{
NumberReached(this, e);//Raise the event
}
return;//don't count any more
}
問的好!如果你想知道為何間接調用,請看OnNumberReached方法的特征:
你看到了,它是保護方法,意味著從這個類繼承的類(子類)中它是可以調用的。
同時它也是虛方法,意味著子類可以改寫它的實現。
那是非常有用的。想象一下你正在設計一個從Counter類繼承的類,通過改寫OnNumberReached方法,可以在事件觸發之前方便的增加一些附加的工作。例如:
{
//Do additional work
base.OnNumberReached(e);
}
注意如果你不調用base.OnNumberReached(e),那么事件永遠也不會觸發。當你繼承了一些類時,你可能想要去除一些事件,這時這樣也許就有用了。有趣的竅門,哈?
一個真實的例子,當你創建一個新的ASP.NET 應用程序時,你去看看后臺產生的代碼,你會發現你的頁面繼承自System.Web.UI.Page類,而且有一個叫OnInit的保護虛方法。其中在里面有一個InitializeComponent()方法被調用用來做一些附加的工作,然后再調用基類的OnInit(e)方法:
protected override void OnInit(EventArgs e)
{
//CODEGEN: This call is required by the ASP.NET Web Form Designer.
InitializeComponent();
base.OnInit(e);
}
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
4、注意NumberReachedEventHandler委托,它是定義在Counter類外,Events命名空間內的,對所有類都可見。
好了,是時候實踐一下我們的Counter類了。
在我們的應用程序中,我們有兩個文本框:txtCountTo和txtReachable

這里是btnRun按鈕點擊事件的事件處理代碼:
{
if(txtCountTo.Text == "" || txtReachable.Text=="")
return;
oCounter = new Counter();
oCounter.NumberReached += new NumberReachedEventHandler(
oCounter_NumberReached);
oCounter.CountTo(Convert.ToInt32(txtCountTo.Text),
Convert.ToInt32(txtReachable.Text));
}
private void oCounter_NumberReached(object sender, NumberReachedEventArgs e)
{
MessageBox.Show("Reached: " + e.ReachedNumber.ToString());
}
這里是初始化某個事件的事件委托的語法:
oCounter_NumberReached);
現在你應該了解到我們正在做什么。我們初始化了NunberReachedEvnetHandler委托(也可以對其他對象)。注意我上面提及的oCounter_NumberReached方法簽名的相似性。
現在來看看我們用=代替+=的情形。
委托是特殊的對象,因為它可以保持多個對象的引用(這里是多個方法)。例如,如果你有另一個方法叫oCounter_NumberReached2,而且簽名和oCounter_NumberReached一樣,那么兩個方法都可以象下面那樣引用:
oCounter_NumberReached);
oCounter.NumberReached += new NumberReachedEventHandler(
oCounter_NumberReached2);
現在,當事件被觸發,一個接一個的方法將被調用。
如果在你的代碼某處,你不想在NumberReached事件觸發時調用oCounter_NumberReached2,你可以這樣做:
oCounter_NumberReached2);
event關鍵字
許多人也許會問:如果我們不用event關鍵字會怎么樣?
使用evnet關鍵字可以阻止任何一個委托的使用者把它設為null。為什么這是重要的?想象一下,一個客戶把我類中的其中一個方法注冊到委托調用鏈表,其他客戶也這樣做,這不會出錯。現在,如果有另一客戶用=代替+=給委托新注冊一個方法。這將會把原來的委托調用鏈表清空,并且創建一個全新的單一的委托在委托調用鏈表中。這時其他客戶將無法接收回復信息。關鍵字event正是針對這一問題提出的,如果我在Counter類中加上event關鍵字,并試著編譯下面的代碼,將產生一個編譯器錯誤信息:

總之,event關鍵字在委托實例上加了一層保護,保護客戶的委托以免被重新設置及委托調用鏈被清空,這樣就只允許對委托調用鏈進行添加或移除操作。
結尾
別忘了在你應用程序的構造函數中聲明以下內容,而不是在cmdRun_Click事件處理代碼中。我那樣做僅僅只為了簡單。;-)
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
oCounter = new Counter();
oCounter.NumberReached += new NumberReachedEventHandler(
oCounter_NumberReached);
oCounter.NumberReached += new NumberReachedEventHandler(
oCounter_NumberReached2);
提供的源代碼就是這樣子的。
posted on 2007-12-30 19:53 nicholas.sun 閱讀(228) 評論(0) 收藏 舉報
浙公網安備 33010602011771號