【 tulaoshi.com - 编程语言 】
回调函数 回调函数的确是至今为止最有用的编程机制之一。C运行时的qsort函数利用回调函数对数组元素进行排序。在Windows中,回调函数更是窗口过程,钩子过程,异步过程调用,以及目前Microsoft .NET框架所必需的,在整个回调过程中自始至终地使用回调方法。人们可以注册回调方法以获得加载/卸载通知,未处理异常通知,数据库/窗口状态修改通知,文件系统修改通知,菜单项选择,完成的异步操作通知,过滤一组条目等等。
在C/C++中,一个函数的地址就是内存地址。这个地址不会附带任何其它赋加信息,如函数的参数个数,参数类型,函数的返回值类型以及这个函数的调用规范。简言之,C/C++回调函数不是类型安全的。
在.NET框架中,回调函数所受到的重用与它在Windows非受控编程中一样。不同的是在.NET框架中提供了一种叫委派(delegates)的类型安全机制。我们先来研究一下委派的声明。下面的代码展示了如何声明,创建和使用委派:
//using System;using System.WinForms;// 在beta2版本中为:System.Windows.Formsusing 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,不同