一.认识委托
在.net框架中,回调函数任然像在非托管windows编程中一样有用和普遍。但是,.net框架为回调函数提供了一种称为委托(delegate)的类型安全的机制。
例如:
class Set { private object[] items; public Set(Int32 numItems) { items = new object[numItems]; for (Int32 i = 0; i < numItems; i++) items[i] = i; } ///
Set类中定义一个公有委托类型Feedback。委托表示一个回调方法的签名。在本例中,Feedback委托表示一个接受3个参数(一个Objcet和两个Int32),并且返回值为void的回调方法。
Set类定义一个名为ProcessItems的公有方法。该方法接受一个参数feedback(一个指向Feedback委托对象的引用),然后遍历Object数组中的所有元素,并对每一个元素调用有feedback变量所指定的回调方法。传递给回调方法的参数分别为待处理元素值、元素序号、以及数组中 元素的总数。回调方法可以选择任何的方式来处理每个元素。
二.委托揭秘
从上的例子看,委托好像很容易使用。但是,整个事情并非前面的例子所演示的那样简单。编辑器CLR在后台做了很多工作来隐藏问题本身的复杂性。
当编译器遇到这段代码时,它会产如下面所示的一个完整的定义:
public class FeedBack : System.MulticasDelegate { //构造器 public FeedBack(Object target, Int32 methodPtr); //下面的方法和源代码中指定的原型一样 public void virtual Invoke(Object value, Int32 item, Int32 numItems); //下面这两个方法允许我们对委托进行异步回调 public virtual IAsyncResult BeginInvoke(Object value, Int32 item, Int32 numItems,AsyncCallback callback,Object object ); public virtual void EndInvoke(IAsyncResult result); }
编译器定义了一个名为FeedBack的类,其继承自.NET框架类库(FCL)中定义的System.MulticasDelegate。由于在源代码中将FeedBack委托类型声明为Pulice,所以编译器产生的FeedBack为一个公共类。如果我们在源码中将其声明为其他修饰符,那么编译器将产生同样的类。委托可以定义在一个类中,也可以定义在一个全局的范围内。因为委托本身就是类,一个类可以在哪里定义,一个委托就可以在哪里定义。
注意所有委托都有一个构造器,并且该构造器接受两个参数:一个对象引用和一个指向回调方法的整数。
在编译器编编译时:它会通过分析源代码来确定我们引用的是那个对象和方法。其中的对象引用会被传递给target参数,一个特殊的标识方法的Int32值会被传递methodPrt参数。对于静态方法而言,null会被传递给target。在构造器内部,这两个参数会被保存在相应的私有字段中。
每个委托对象实际上是对方法及其调用时操作的对象的一个封装。MuliticastDlegate类定义了两个只读公有实例属性:Target和Method。给定一个委托对象的引用,我们可以查询这些属性。Target属性返回一个方法回调是操作的对象引用。如果是静态方法,Target将返会null。Method属性返回一个标识回调方法的System.Relfection.MethodInfod对象。
例如
我们查看一个委托对象是否引用着一个特定类型的实例方法:
Boolean DelegateRefersToInstanceMethodOfType(MutivastDelegate d,Type type)
{
return (d.Target!=null&&d.Target.GetType()==type);
}
我们可以查看回调方法是否有着一个特定的名称
Boolean DelegateRefersToMehtodOfName(MutivastDelegate d,string methodName)
{
return (d.Method.Name==methodName);
}
在Set类中ProcessItems方法中:
注释下面就是调用回调方法的代码,这里实际上没有什么feedback函数。编译器在编译时知道feedback是一个指向委托对象的变量,所以它会产生代码来调用委托对象的Invoke方法。
他将源代码中的
feedback(items[item], item += 1, items.Length);
编译为:
feedback.Invoke(items[item], item += 1, items.Length);
但是在c#中不允许我们显示的调用Invoke方法,将会报:“error CS1533 Invoke无法直接在委托中调用”。
编译器在定义Feedback类时定义Invoke方法,它的签名和Feedback委托的签名是相匹配的。
例如我们上面的例子,Feedback接受了3个参数并且返回void,所以Invoke方法也接受同样的3个参数,返回void。
-----