注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

心情挺好的博客

正在等你光临呢 呵呵

 
 
 

日志

 
 
关于我

喜欢摄影的朋友看过来:) 有时间就跟我一起去拍照去吧. QQ272751 上海圣玛丽摄影化妆培训学校 16年专业摄影培训化妆培训学校 电话:15900513500。 http://www.smlsh.com

网易考拉推荐

使用 C# 談談委派 (Delegate)  

2008-01-20 00:40:38|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

 

使用 C#  

談談委派 (Delegate)

Eric Gunnerson

Microsoft Corporation

2001 年 4 月 15 日

一點回顧

上個月我們談到效能和 Box 動作。讀者提出的問題之一是,為什麼 Perl 版會比 C# 版快那麼多。答案之一是 Perl 非常擅長做這種事情。有一位讀者指出,我忘了與其在每一個符合的項目上都加以解譯,不如讓 Regex 來編譯規則運算式 (Regular Expression)。在 Beta 1 中,解決方法是傳遞「c」做為 Regex 建構函式的第二個參數 (在 Beta 2 中則用列舉來做同樣的事情)。這樣可以將成本減少幾乎一半,並將最快的版本所需的時間降低到略低於 7 秒鐘。

委派和事件

本月專欄的主題是委派 (Delegate) 和事件。呃,其實只有委派而已,因為事件是下個月的主題。我會先解釋委派是什麼、能做什麼、以及如何使用,然後下個月再來談事件。

委派

在大部分情況中,我們在呼叫函式時,都會直接指定要呼叫的函式。如果 MyClass 類別擁有一個叫做 Process 的函式,我們通常會以下列方式呼叫它:

MyClass myClass = new MyClass();myClass.Process();

這在大部分情況下都行得通。不過,有時候我們不想直接呼叫函式—而希望能夠將它傳遞給別人,供他們呼叫。這在事件驅動系統中尤其有用,例如在圖形使用者介面中,我希望某些程式碼在使用者按一下按鈕時被執行、或者我要記錄某些資訊,但是卻無法指定如何記錄它時。

請參考下列範例:

public class MyClass{ public void Process() { Console.WriteLine("Process() begin"); // other stuff here? Console.WriteLine("Process() end"); }}

在這個類別中,我們正在做一些記錄,以便知道函式何時開始和結束。不幸的是,我們的記錄只能硬接到主控台,而這可能並不是我們要的。我們真正想要的是,有辦法控制資訊要從函式外的哪裡記錄,而不必讓函式程式碼變得更複雜。

委派正好適合這個用途,它可以讓我們指定要呼叫的函式的模樣,而不必指定要呼叫哪一個函式。委派的宣告看起來就像是函式的宣告,只不過在這個案例中,我們宣告的是這個委派可以參考的函式的簽名碼 (Signature)。

就我們的範例來說,我們要宣告接受單一字串參數而且沒有傳回型別的委派。我們的類別修改如下:

public class MyClass{ public delegate void LogHandler(string message); public void Process(LogHandler logHandler) { if (logHandler != null) logHandler("Process() begin"); // other stuff here? if (logHandler != null) logHandler ("Process() end"); }}

使用委派就像直接呼叫函式一樣,不過,在呼叫函式之前,我們需要加上一項檢查,看看委派是否為 Null (也就是說,沒有指向函式)。

如果要呼叫 Process() 函式,我們需要宣告與委派相符的記錄函式,再建立指向函式的委派的執行個體。然後這個委派會被傳遞給 Process() 函式。

class Test{ static void Logger(string s) { Console.WriteLine(s); } public static void Main() { MyClass myClass = new MyClass(); MyClass.LogHandler lh = new MyClass.LogHandler(Logger); myClass.Process(lh); }}

Logger() 函式就是我們想要從 Process() 函式呼叫的函式,而且它會被宣告以便符合委派。我們在 Main() 中建立委派的執行個體,並將委派建構函式傳遞給我們要建構函式指向的這個函式。最後將委派傳遞給 Process() 函式,才能夠呼叫 Logger() 函式。

如果您是 C++ 的使用者,可能會認為委派很像函式指標,您的想法算是很正確。不過,委派不只是函式指標而已,它還提供其他功能。

傳遞狀態

在上面的簡單範例中,Logger() 函式只有寫出字串。不同的函式可能會想將資訊記錄到檔案中,不過,要這樣做,函式必要知道要將資訊寫入那個檔案。

於 Win32® 中,在傳遞函式指標時,您可以將狀態一起傳遞出去。不過,在 C# 中就沒有必要這樣做,因為委派可以參考靜態或成員函式。底下是如何參考成員函式的範例:

class FileLogger{ FileStream fileStream; StreamWriter streamWriter; public FileLogger(string filename) { fileStream = new FileStream(filename, FileMode.Create); streamWriter = new StreamWriter(fileStream); } public void Logger(string s) { streamWriter.WriteLine(s); } public void Close() { streamWriter.Close(); fileStream.Close(); }}class Test{ public static void Main() { FileLogger fl = new FileLogger("process.log"); MyClass myClass = new MyClass(); MyClass.LogHandler lh = new MyClass.LogHandler(fl.Logger); myClass.Process(lh); fl.Close(); }}

FileLogger 類別只會封裝檔案。修改 Main() 才能讓委派指向 FileLogger 的

fl

執行個體上的 Logger() 函式。在從 Process() 叫用這個委派時,就會呼叫成員函式,並將字串記錄到適當的檔案中。

酷的是我們不必變更 Process() 函式;不論是否參考靜態或成員函式,所有委派的程式碼都是一樣的。

多點傳送

能夠指向成員函式固然好,但是用委派做的還不止於此。在 C# 中,委派是多點傳送的,這表示它們可以同時指向一個以上的函式 (也就是說,它們不以 System.MulticastDelegate 型別為基礎)。多點傳送委派有一份函式清單,當委派被叫用時,清單中的所有函式也都會被呼叫。我們可以在第一個範例的記錄函式中加回去,並呼叫兩個委派。為了合併兩個委派,我們要使用 Delegate.Combine() 函式。程式碼如下所示:

MyClass.LogHandler lh = (MyClass.LogHandler) Delegate.Combine(new Delegate[] {new MyClass.LogHandler(Logger), new MyClass.LogHandler(fl.Logger)});

哇,真是難看!與其強迫使用者接受這個語法,不如由 C# 提供更好的語法。您可以不要呼叫 Delegate.Combine(),而只要用

+=

將兩個委派合併起來即可:

MyClass.LogHandler lh = null; lh += new MyClass.LogHandler(Logger); lh += new MyClass.LogHandler(fl.Logger);

這樣就清爽多了。如果要將委派從多點傳送委派中移除,可以呼叫 Delegate.Remove(),或者使用

-=

運算子 (我知道我會用哪一個)。

呼叫多點傳送委派時,叫用清單中的委派會依它們出現的順序被同步呼叫。如果處理序期間發生錯誤,執行處理序會中斷。

如果想更密切的控制叫用順序—例如,如果您想要有保證的叫用—可以從委派取得叫用清單,並自行叫用函式。方法如下所示:

foreach (LogHandler logHandler in lh.GetInvocationList()){ try { logHandler(message); } catch (Exception e) { // do something with the exception here? }}

程式碼在 Try-Catch 中只會包裝各項呼叫,所以處理常式中擲回的例外狀況 (Exception) 並不會妨礙其他處理常式被呼叫。

事件

我們已經談了不少委派,現在該來談談事件了。一個顯而易見的問題是:「既然已經有了委派,為什麼還要事件?」

最好的回答就是,研究一下使用者介面物件的案例。例如,按鈕上面可能有一個公用的 Click 委派。我們可以將函式連結到該委派,那樣在按下按鈕時就會呼叫委派。我們可以這樣做:

Button.Click = new Button.ClickHandler(ClickFunction);

這表示在按下按鈕時,就會呼叫 ClickFunction()。

問題:上面的程式碼有問題嗎?我們忘了什麼?

答案是我們忘了使用

+=

,而直接指定了委派。這表示任何連結到 Button.Click 的其他委派都會被解除攔截。Button.Click 必須是公用 (Public) 的,其他物件才能存取它,而我們無法阻止上述的情況。同樣的,如果要移除委派,使用者可以這樣寫:

Button.Click = null;

這樣會移除所有委派。

這些情況特別糟糕,因為在很多案例中,都只連結一個委派,問題不會明顯到被當成錯誤。若日後連結了另一個委派,問題就開始惡化了。

事件會在委派模型上方加一層保護。底下是可支援事件的物件之範例:

public class MyObject{ public delegate void ClickHandler(object sender, EventArgs e); public event ClickHandler Click; protected void OnClick() { if (Click != null) Click(this, null); }}

ClickHandler 委派會用事件委派的標準模式來定義事件的簽名碼。它的名稱以處理常式結尾,而且有兩個參數。第一個參數是傳送物件的物件,第二個參數則用來傳遞和事件一起發出的資訊。這個案例沒有要傳遞的資訊,所以直接使用 EventArgs,但是有資料要傳遞時,就要使用從 EventArgs 衍生的類別 (例如 MouseEventArgs)。

宣告 Click 事件要做兩件事。首先,宣告名為 Click 的委派成員變數,這個變數會從類別內部被使用。其次,宣告名為 Click 的事件,這個事件可以根據一般的存取規則,從類別外部被使用 (在這個案例中,事件是公用的)。

通常都會包括一個像 OnClick() 的函式,使得型別或衍生型別可以引發事件,您會發現,它用來引發事件的程式碼和委派的程式碼一樣,因為 Click 是一個委派。

就像委派一樣,我們攔截和取消攔截使用

+=

-=

的事件,和委派不同的地方,在於這些是唯一能在事件上執行的作業。這樣可以確保先前談到的兩項錯誤不會發生。

事件的使用方式簡單明瞭。

class Test{ static void ClickFunction(object sender, EventArgs args) { // process the event here. } public static void Main() { MyObject myObject = new MyObject(); myObject.Click += new MyObject.ClickHandler(ClickFunction); }}

我們建立和委派的簽名碼相符的靜態函式或成員函式,然後用

+=

將委派的新執行個體加入事件。

本月到此為止。下個月我們要談一下事件如何在內部運作,以及實作事件的一些進階方法 (提示:事件有時候因為有額外保護而略不同於委派)。如果您真的好奇地等不及了,範例程式碼裡也有下個月的程式碼。

酷站收集

很不幸,這個月我的手提電腦有問題,傳送給我的網站都遺失了。如果您有傳送 URL 給我,卻還沒有在網站上看到,請重傳到 ericgu@microsoft.com 給我。 

 

from http://msdn.microsoft.com/library/cht/default.asp?url=/library/CHT/dncscol/html/csharp04192001.asp

  评论这张
 
阅读(1574)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017