回調函數 回調函數的確是至今為止最有用的編程機制之一。C運行時的qsort函數利用回調函數對數組元素進行排序。在Windows中,回調函數更是窗口過程,鉤子過程,異步過程調用,以及目前Microsoft .NET框架所必需的,在整個回調過程中自始至終地使用回調方法。人們可以注冊回調方法以獲得加載/卸載通知,未處理異常通知,數據庫/窗口狀態修改通知,文件系統修改通知,菜單項選擇,完成的異步操作通知,過濾一組條目等等。
在C/C++中,一個函數的地址就是內存地址。這個地址不會附帶任何其它賦加信息,如函數的參數個數,參數類型,函數的返回值類型以及這個函數的調用規范。簡言之,C/C++回調函數不是類型安全的。
在.NET框架中,回調函數所受到的重用與它在Windows非受控編程中一樣。不同的是在.NET框架中提供了一種叫委派(delegates)的類型安全機制。我們先來研究一下委派的聲明。下面的代碼展示了如何聲明,創建和使用委派:
//
using System;
using System.WinForms; // 在beta2版本中為:System.Windows.Forms
using System.IO;
class Set {
private Object[] items;
public Set(Int32 numItems) {
items = new Object[numItems];
for (Int32 i = 0; i < numItems; i++)
items[i] = i;
}
// 定義 Feedback,類型為delegate
// (注意: 這個類型在Set類中是嵌套的)
public delegate void Feedback(
Object value, Int32 item, Int32 numItems);
public void ProcessItems(Feedback feedback) {
for (Int32 item = 0; item < items.Length; item++) {
if (feedback != null) {
// 一旦指定了回調,便調用它們
feedback(items[item], item + 1, items.Length);
}
}
}
}
class App {
[STAThreadAttribute]
static void Main() {
StaticCallbacks();
InstanceCallbacks();
}
static void StaticCallbacks() {
// 創建一個Set對象,其中有五個項目
Set setOfItems = new Set(5);
// 處理項目,feedback=null
setOfItems.ProcessItems(null);
Console.WriteLine();
// 處理項目,feedback=Console
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToConsole));
Console.WriteLine();
// 處理項目,feedback =MsgBox
setOfItems.ProcessItems(new Set.Feedback(App.FeedbackToMsgBox));
Console.WriteLine();
// 處理項目,feedback = console AND MsgBox
Set.Feedback fb = null;
fb += new Set.Feedback(App.FeedbackToConsole);
fb += new Set.Feedback(App.FeedbackToMsgBox);
setOfItems.ProcessItems(fb);
Console.WriteLine();
}
static void FeedbackToConsole(
Object value, Int32 item, Int32 numItems) {
Console.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
}
static void FeedbackToMsgBox(
Object value, Int32 item, Int32 numItems) {
MessageBox.Show(String.Format("Processing item {0} of {1}: {2}.",
item, numItems, value));
}
static void InstanceCallbacks() {
//創建一個Set對象,其中有五個元素
Set setOfItems = new Set(5);
// 處理項目,feedback=File
App appobj = new App();
setOfItems.ProcessItems(new Set.Feedback(appobj.FeedbackToFile));
Console.WriteLine();
}
void FeedbackToFile(
Object value, Int32 item, Int32 numItems) {
StreamWriter sw = new StreamWriter("Status", true);
sw.WriteLine("Processing item {0} of {1}: {2}.",
item, numItems, value);
sw.Close();
}
}
//
注意代碼最上面的Set類。假設這個類包含一組將被單獨處理的項目。當你創建Set對象時,將它要操縱的項目數傳遞給它的構造函數。然後構造函數再創建對象(Objects)數組並初始化每一個整型值。
另外,Set類定義了一個公共的委派,這個委派指出某個回調函數的簽名。在這個例子中,委派Feedback 確定了一個帶三個參數的方法(第一個參數為Object,第二和第三個參數都是Int32類型)並且返回void。在某種意義上,委派很像C/C++中表示某個函數地址的類型定義(typedef)。
此外,Set類定義了一個公共方法:ProcessItems。這個方法有一個參數feedback——一個對委派Feedback 對象的引用。ProcessItems迭代遍歷所有的數組元素,並且針對每一個元素調用回調方法(由feedback變量指定哪一個會調方法),這個回調方法被傳遞,從而以不同的方式處理回調方法所傳遞的項目值,項目數量以及數組中的元素數目。可以看出回調方法能以它選擇的任何方式處理每一個項目。
使用委派調用靜態方法 StaticCallbacks方法示范了用各種不同方式的回調委派。這個方法首先構造一個Set對象,告訴它對象創建有五個對象元素的數組。然後調用ProcessItems,在第一個調用中,它的feedback參數為null。ProcessItems呈現一個方法,這種方法為每一個Set操縱的項目實現某種動作。在第一個例子中,因為feedback參數是null,在處理每一個項目時不調用任何回調方法。
第二個例子中創建了一個新的Set.FeedBack委派對象,這個委派對象是一個方法包裝器,允許方法的調用是經由這個包裝器間接調用。對於類型FeedBack的構造器來說,方法的名字(App.FeedBackConsole)被作為構造器的參數傳遞;這就表示方法被包裝。然後,從new操作符返回的引用被傳到ProcessItems。現在,當執行ProcessItems時,它會調用App類型的FeedbackToConsole方法處理集合中的每一個項目。FeedbackToConsole簡單地將一個串輸出到控制台,表示哪個項目被處理了以及這個項目的值是什麼。
第三個例子與第二個例子基本相同。唯一的差別是Feedback委派對象包裝的是另一個方法:App.FeedbackToMsgBox。這個方法建立一個串,用這個串表示哪個項目被處理以及這個項目的值是什麼。然後將這個串顯示在一個信息框中。
第四個例子也是靜態調用的最後一個例子示范了如何將委派鏈接在一起形成一個鏈。在這個例子中,首先創鍵一個Feedback委派對象的引用變量fb,並將它初始化為null。這個變量指向委派鏈表的頭。Null值表示鏈表中沒有節點。然後,構造Feedback委派對象,由這個對象包裝對App FeedbackToConsole方法的調用。C#中,+=操作符被用於將對象添加到fb引用的鏈表中。Fb此時指向鏈表的頭。
最後,構造另一個Feedback委派對象,由這個對象包裝對App FeedbackToMsgBox方法的調用。C#中的+=操作符又一次被用於將對象添加到fb引用的鏈表中,並且fb被新的鏈表的頭更新。現在,當執行ProcessItems時,傳遞給它的是Feedback委派鏈表的頭指針。在ProcessItems內部,調用回調方法的代碼行實際上終止調用所有的在鏈表中由委派對象包裝的回調方法。也就是說,對於被迭代的每一個項目,都會調用FeedbackToConsole,接著馬上調用FeedbackToMsgBox。在後續文章中我將詳細討論委派鏈的處理機制。
有一點很重要,那就是在這個例子中每件事情都是類型安全的,例如,當構造Feedback委派對象時,編譯器保證App的FeedbackToConsole和FeedbackToMsgBox方法都具備確切的原型,像由Feedback委派定義的一樣。既兩個方法必須有三個參數(Object,Int32和Int32),並且兩個方法必須有相同的返回類型(void)。如果方法原型不匹配,則編譯器將發出下面的出錯信息:“error CS0123:The signature of method App.FeedbackToMsgBox() does not match this delegate
type。”——意思是App.FeedbackToMsgBox()方法的簽名與委派的類型不匹配。
調用實例方法 前面我們討論了如何使用委派調用靜態方法。但是委派還能被用於調用特定對象的實例方法。在調用實例方法時,委派需要知道這個它要用方法操作的對象的實例。
為了理解實例方法的回調機制,讓我們回頭看看前面代碼中的InstanceCallbacks方法。這段代碼與靜態方法的情形極其相似。注意在Set對象被創建之後,App對象被創建。這個App對象僅僅是創建而已,處於示例目的沒有其它內容。當新的Feedback委派對象被創建的時候,它的構造齊備傳到appobj.FeedbackToFile。這將導致這個委派包裝對FeedbackToFile方法的引用,FeedbackToFile是個實例方法(非靜態)。當這個實例方法被調用時,由appobj引用的對象被操作(作為隱藏傳遞參數)。FeedbackToFile方法的作用有點像FeedbackToConsole 和 FeedbackToMsgBox,不同