posts - 8, comments - 42, trackbacks - 0, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

学习委托(2)-------解析委托的实现机制

Posted on 2006-07-13 12:16 李玉锋 阅读(...) 评论(...) 编辑 收藏
 

     在前一篇文章中,从函数指针的角度谈论了委托,本篇文章来看.NET是如何为我们实现委托的。为了讨论方便,把上一次C#委托的代码写在下面:

class liyufeng

    {

        public static void mySay()

        {

            System.Console.WriteLine("I Like You,Do you know,if you don’t know,now I must tell you ,tell you!");

        }

    }

class goodfriend

    {

        public delegate void middleSay();

        public static void friendSay(middleSay say)

        {

            say();

        }

    }

class beautifulpark

    {

        static void Main(string[] args)

        {

            goodfriend.friendSay(liyufeng.mySay);

            System.Console.Read();

        }

    }

   首先看委托的声明public delegate void middleSay()前面提到过,委托是函数指针封装成了对象,也就是说委托也应该是一个类,那么这里是通过Delegate关键字,而不是用声明类的关键字Class,也很显然,从上面这句代码无论如何也看不出来委托是一个类,表面上看它似乎是一个方法。要弄清楚这一点,我们需要用到ILdasm这个查看MSIL代码的工具,通过它便可一目了然,其实.NET编译器背后为我们做了很多的工作。下面是声明委托middleSayIL代码:

.class auto ansi sealed nested public middleSay

       extends [mscorlib]System.MulticastDelegate

{

    .method public hidebysig specialname rtspecialname

        instance void .ctor(object 'object',

                             native int 'method') runtime managed

    {

    }

    .method public hidebysig newslot virtual

        instance class [mscorlib]System.IAsyncResult

        BeginInvoke(class [mscorlib]System.AsyncCallback callback,

                    object 'object') runtime managed

    {

    }

    .method public hidebysig newslot virtual

        instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed

    {

    }

    .method public hidebysig newslot virtual

        instance void Invoke() runtime managed

    {

    }

}

关于上面IL代码的语法我们不必太理会,也可以明白的看出我们声明的委托middleSay的确是一个从MulticastDelegate继承而来的类,并且有一个构造函数和三个成员函数。只是.NET编译器不允许我们自己直接从MulticastDelegate显式继承,否则上面的代码我们完全可以自己实现。看一下MulticastDelegate类的声明:

public abstract class MulticastDelegate : Delegate

{

}

可以看出它是一个抽象类,并且从Delegate继承;前面文章曾提到过,我们可以利用委托一次调用多个函数,所以这里的MulticastDelegate,从字面也可大概猜出它的功能,它主要是为我们准备了一个委托对象的链表,从而为我们调用多个委托函数提供了支持,也就是大家常说的多播委托,而它的父类Delegate,为调用一个委托函数提供必要的支持,微软本意是这么设计的,但是其实我们声明的委托全部是从MulticastDelegate继承而来的,也就说单播委托和多播委托的实现机制是相同的,无论单播还是多播都会被MulticastDelegate保存到自身的链表中,在进行委托函数调用时,都是一个个的按次序轮流调用,直到把委托链表中的委托对象全部都调用一遍。关于MulticastDelegateDelegate,应该说是微软设计上的一个失误,Jeffrey Richter先生在书中提到微软应该在.NET未来的版本中将这两个类合并到一起,但到目前的2.0版本,它们仍然还未合并。其实从面向对象的角度来理解,感觉倒也不错,Delegate为父类,提供了委托基本的属性和行为,像单播委托;而MulticastDelegateDelegate继承,理所当然保留父类的一切,并且为了支持多播委托,而进行了功能上的扩充。如果从其本身功能的角度理解,可能会有点迷惑,既然Delegate主要提供单播委托支持,而MulticastDelegate主要提供多播委托支持,那么单播委托理论上应该从Delegate继承比较适宜,而多播委托应该从MulticastDelegate继承较为妥当,然而不尽然的是两者均从MulticastDelegate继承而来,这么一来确实有点让人摸不着头脑了。(有关这里的详细内容请参看Jeffrey Richter先生原著李建忠先生翻译的《.NET框架程序设计(修订版)》)

下面来看对委托函数的调用

        public static void friendSay(middleSay say)

        {

            say();

        }

看样子和我们调用一个函数的语法没什么两样,只是这里是通过委托变量来调用的(此处,say是一个middleSay委托类型的变量),如果声明的委托原型有参数的话,比如

public delegate void middleSay(string myStr)在这里调用的时候我们还可以给委托传递参数,像这样:say("I Love you");但实际上我们知道,say只是一个委托类型的变量,它的内容是托管堆上该委托变量的引用地址,而不像函数指针,就是一个真实的函数地址,我们通过函数指针调用函数,直接跳到该地址处就可以了,但通过委托变量进行调用这点显然是行不通的。这个时候.NET编译器为我们带来了福音,还记得前面的IL代码吗?编译器为我们生成了三个实例方法,其中有一个Invoke()方法,而且该方法的返回值和参数与我们声明的委托middleSay是完全匹配的,关键就是它了,编译器在这里遇到say();会非常自然的转换成对这个委托对象的调用,也就是像这样:say.Invoke();这点我们通过IL代码也可以看出来,下面是该委托调用的IL代码:

.method public hidebysig static void friendSay(class ConsoleApplication1.goodfriend/middleSay say) cil managed

{

 .maxstack 8

 nop

 ldarg.0

 callvirt   instance void ConsoleApplication1.goodfriend/middleSay::Invoke()

  nop

 ret

}

看起来应该是可以了,但还有一个问题,Invoke()方法如何知道要调用委托方法的信息,比如我们这里要调用的是liyufeng 类中的mySay方法,编译器是如何让Invoke定位到liyufeng 类的方法mySay呢?这就又需要MulticastDelegateDelegate的配合了,想看到其中秘密还得借助ILdasm这个工具,用它来查看mscorlib.dll这个程序集,再看Delegate类的信息,可以看到有_methodPtr_target两个私有字段,_methodPtr就是用来标识委托方法的,我的理解它就和我们真正的函数指针差不多了,是一个函数地址;_target是用来标识委托方法所在的对象,当然我这个例中由于需要回调的方法mySay是一个静态方法,所以_target会被编译器设置为null;如果把mySay改为一个实例方法的话那么_target就会指向一个对象实例,此处即应该为liyufeng类的一个实例。编译器在通过Invoke对委托方法进行调用时就是根据_methodPtr_target进行定位的。讨论到这里,不知道大家还记不记得上一篇文章中有个问题,就是在C++中通过函数指针进行函数调用,该函数如果被封装在类中的话,必须是静态函数,这是因为在C++中,调用非静态函数时,总会隐含的传递一个当前对象实例的this指针,以便标识是对当前对象成员的调用,而调用静态函数时就不会有这个隐含的this指针,所以如果你通过函数指针对非静态函数进行调用时,由于被调用的非静态函数多了一个this参数,无法和声明的函数指针原型匹配,这就是为什么必须为静态函数的原因了。.NET下编译器为我们生成的_target是不是和this指针相似呢?至少通过_target私有字段,.NET为我们解决了调用实例方法(也就是非静态函数)的难题。

总之,.NET通过MulticastDelegateDelegate两个类,再加上编译器的从中配合,三者很好的演绎了委托这一强大的技术,虽然我们再平时的编成中对他们接触的可能不多,但我感觉认识一下他们也是不错的,至少能加深对委托的理解。

 

注:本文参考了Jeffrey Richter先生原著李建忠先生翻译的《.NET框架程序设计(修订版)》这本书,可以说是对读这本书的一点感受吧;如果本文有什么问题,请大家参考Jeffrey Richter先生的原著。