c#中的Delegate解析

  在我的上一篇blog< 从面向对象编程的角度解析c#中的事件处理机制>中,我对c#的事件处理机制设计原理从面向对象的角度解析了其原理,文中最后提到委托是函数的模板,有几个读者回头就跟我说,既然它只是一个模板来负责规范事件响应函数的声明,那么,只要我的所有对该事件的响应函数都符合事件的函数调用声明的规范,那么,我就是不是可以不定义delegate了呢?很好,很好的一个问题,谁说不是呢?

      我的这篇blog正是为这个问题而作。

      在<从面向对象编程的角度解析c#中的事件处理机制>中的末尾,我提到public event BabyIllEventHander BabyIsILL这句声明通过c#编译器编译后,变成了如下可以语句:

// 1 一个初始化为NULL的“私有”委托类型字段
private  BabyIllEventHandler babyIll = null ;

// 2 一个允许对象登记事件的add_*方法
[MethodImplAttribute(MethodOptions.Synchronized)]
public   void  add_BabyIll(BabyIllEventHandler handler)
{
    babyIll
= (BabyIllEventHandler)Delegate.Combine(babyIll,handler);
}

// 3 一个允许对象注销事件的remove_*方法
[MethodImplAttribute(MethodOptions.Synchronized)]
public   void  remove_BabyIll(BabyIllEventHandler handler)
{
    babyIll
= (BabyIllEventHandler)Delegate.Remove(babyIll,handler);
}

 

    下面,我们就从这里开始讲起。

    我们发现,我们寄予厚望----我们观念中觉得会很重要的一个关键字“Event”经过编译之后,没有了,而那个我们定义的Delegate : BabyIllEventHandler又冒出来,何也?或许这就是玄机之所在。

   “Delegate是函数的模板,Event是函数的容器”,这是我在<从面向对象编程的角度解析c#中的事件处理机制>中的表达。现实生活中,我们常说“容器”,是因为这个东西可以往里面装东西,可以往里面取东西。而在我们编程的概念中,一个Container往往有Add方法和Remove方法来实现对里面Object的添加,移出。如今,这个Event却没有这些方法,可见,它不是一个真正的容器。是的,没错,它是一层包装纸,是真正的容器的包装纸!真正的容器是那个Delegate。用技术的概念来说,Event是微软提供给我们的一个“语法甜饼”(syntactic sugar),这个语法甜饼把Delegate封装起来,达到更易用的目的。

  仔细看一看,事件的登记和注销是通过调用System.Delegate 的静态方法Combine()Remove()来实现对“容器”babyIll(Delegate :BabyIllEventHandler 的实例)的增加,移出。Combine() 将第二个 Delegate 结合到第一个 Delegate 中,传回此一新的 Delegate,转换类型之后重新赋给babyIll来实现增加Remove() 将第二个 Delegate 自第一个 Delegate 中移除,并传回此一新的 Delegate转换类型之后重新赋给babyIll来实现移出。如此说来,Delegate才是我们真正需要花大力气关注的东西。

    下面,我们来看看Delegate。

     我们先来谈谈回调函数,说说C语言中的吧。在学c语言的时候,我们只看到过函数的说法,现在冒出个回调函数是怎么回事?其实,回调函数本质上就是函数,在定义上是没有区别的。在编程过程中,有些函数是你写并被你自己调用,这就是我们常说的“过程函数”,而另一些函数是由你来写但是在某些情况下不光由你来调用还可能被系统调用或者是别人调用,这种函数就是“回调函数”。你的函数要被系统/别人要调用,那么你的函数就要遵守别人给出的接口规范,这就像我们c#中事件响应函数要符合事件源中定义的函数声明规范(也就是委托中的声明)一样。在设置回调函数时,将你的回调函数的地址(也就是函数指针)作为参数送给系统。当系统调用时(如事件发生,启动功能...),就自动会执行你的回调函数。可惜,该地址不会携带任何额外的信息,例如函数期望的参数个数,参数类型,参数的返回值以及函数的调用约定。也就是说,虽然系统/别人要求你要符合规范,但是因为没有在原理上规避,使得你可以通过一些特殊的处理方式达到挂接“非法”函数的目的,这就意味着,c语言中的回调函数不是类型安全的。

    委托是.net Framework提供的用来实现回调函数的机制,并且这种机制是类型安全的(微软不是总是宣称c#是类型安全的吗?这就是一例)。下面,我们来看看,它是怎么实现函数回调的,何以见得是类型安全的呢?

    我们先来看看Delegate的构造:

    在<从面向对象编程的角度解析c#中的事件处理机制>中,定义BabyIllEventHandler 这个委托类型的语句如下:

   public delegate void BabyIllEventHandler(object sender,BabyILLEventAgrs args);

   当编译器遇到这段代码时,它会产生如下所示的一个完整的类定义:    你可以通过Reflector看一看,确实编译器产生了这样的一个类。包括一个构造函数,一个对委托函数的同步调用方法(Invoke()),两个异步回调(BeginInvoke(),EndInvoke())。我们还注意到这个类定义继承自System.Multicastdelegate。为什么是System.Multicastdelegate,而不是System.Delegate呢?这其中是有一段历史的,因为不是我们关注的重点,这里就不提了。

public   class  BabyIllEventHandler:System.MulticastDelegate
{   
// 构造函数
   public  BabyIllEventHandler( object  target,Int32 methodPtr);
     
// 下面的方法和委托定义中指定的原型一样      // 同步调用方法
    public   void    virtual  Invoke( object  sender,BabyIllEventArgs args);
      
// 异步调用
   public    virtual  IAsyncResult BeginInvoke( object  sender,BabyIllEventArgs args,AsyncCallBack callback, object   object );

  
public    virtual   void  EndInvoke(ISyncResult result);
}

 

   所有的委托类型都继承自System.Multicastdelegate,在System.Multicastdelegate的成员中,有三个很重要的字段是我们需要关注的:

字段类型描述
_targetSystem.Object指向回调函数被调用时应该被操作的对象。在事件处理机制中,对应事件的响应对象。如果是响应函数是静态方法,它会被置为Null
_methodPtrSystem.Int32回调函数的内存地址,也就是函数指针。在事件处理机制中,对应事件的响应函数的内存地址。
_preSystem.MultiDelegate指向另外一个委托对象。通过这个指针,形成了委托链

  通过描述中的说明,我们可以认识到,一个委托对象实际上是对调用时操作的对象和回调方法的一个封装。

      我们已经知道委托对象是怎样构造的,下面我们来看看回调函数是怎样调用的。这里只谈同步调用的情况,异步调用的情况可以参考相关资料。

      看事件定义:    public event BabyIllEventHander BabyIsILL;
      通过前面的分析,实际上BabyIsIll是委托:BabyIllEventHander 类型的,编译器知道BabyIsIll是一个指向委托对象的变量(严格来说,应该是指向一个委托对象容器的变量。这个容器是通过委托对象的_pre来指向前一个委托对象的,因此BabyIsIll指向最后一个委托对象即可),所以,它会产生代码来调用BabyIllEventHander 的Invoke()方法,这样,当我们使用BabyIsIll(this,e)来激发事件的时候,实际上是它会去调用Invoke()方法,当Invoke方法被调用时,它使用_target和_methodPtr两个私有字段在指定的对象(_target中指定)上调用期望的方法(_methodPtr中保存了方法的指针)。

     同时,注意Invoke()方法的签名和BabyIllEventHander定义的签名相匹配。正是因为这种配置关系,要求调用回调函数的时候进行了参数检查,返回值检查,微软才拍着胸脯大胆的说,这是类型安全的。

     [全文完]

      在这篇blog中参考了《.net框架程序设计(修订版)》第17章《委托》中的内容。另外,如果大家对事件处理以及函数指针感兴趣的话,可以看看台湾作家蔡学庸的《函数指针的进化论(1)》,绝对值得推荐的一个系列,有三篇文章,点击链接进入页面后面,其下方有2和3的链接。看完这三篇文章后,我总感觉,如果我不推荐给大家,就是我的错;但是,如果你不去看,或者不认真的看,那就是你的错。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值