c#委托(delegate)揭秘

委托是一种定义方法签名的类型。 当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。 您可以通过委托实例调用方法。  

在表面上,委托很简单,使用new来构造委托实例。使用委托实例的变量名来调用回调函数。实际情况是编译器,CLR在幕后做了大量的工作来隐藏其复杂性,只有了解了这些幕后的东西,你才能真正的掌握它、灵活的运用它。

     1、声明委托

namespace DelegateDemo
{
    internal delegate void HelloCallBack(string name);
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}

 

    通过ildasm查看中间代码,如下

编译器自动生成一个helloCallBack的类,类里面有构造方法,回调方法Invoke,异步回调方法(BeginInvoke,EndInvoke),

它继承MulticastDelegate类,MulticastDelegate继承Delegate类,c#有两个委托类(Delegate,MulticastDelegate)是有历史原因的,原来是要合并成一个类,但快到发布时间了,合并它需要重新测试,所以Delegate就幸存下来)。

  2、委托的实例化

    internal delegate void HelloCallBack(string name);
    class Program
    {
        static void Main(string[] args)
        {
            HelloCallBack helloShow = new HelloCallBack(ShowName);

            Console.ReadLine();
        }

        static void ShowName(string name)
        {
            Console.WriteLine(name);
        }        
    }

 

      中间代码如下

     .method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint  /                                                                                                 //第一个被执行的方法被称为入口函数
  // Code size       15 (0xf)
  .maxstack  3                                                                                                  //定义函数代码所用堆栈的最大深度
  .locals init ([0] class DelegateDemo.HelloCallBack helloShow)                                //分配一个局部变量helloShow

  IL_0000:  nop                                                                                                 //如果修补操作码,则填充空间,未执行任何有意义的操作

  IL_0001:  ldnull                                                                                                //将空引用推送到计算堆栈上
  IL_0002:  ldftn      void DelegateDemo.Program::ShowName(string)                      //将ShowName的函数指针(非托管指针 native int 类型)推送到计算堆栈上
  IL_0008:  newobj     instance void DelegateDemo.HelloCallBack::.ctor(object, native int) //创建一个新对象,并将对象引用推送到计算堆栈上
  IL_000d:  stloc.0                                                                                               //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量helloShow中
  IL_000e:  ret                                                                                                    //从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上
} // end of method Program::Main

  从中间代码我们可以看到,HelloCallBack类构造函数有两个参数,HelloCallBack::.ctor(object,  native int),而我的代码是

HelloCallBack helloShow = new HelloCallBack(ShowName);只有一个参数,应该编译不过。编译器在这个地方帮我们做了一些东西,

当它知道要构造的是委托时,就会分析源代码来确定引用的是哪个对象,那个方法。对象引用传递给object,ShowName的函数指针传递给native int

    3、调用回调方法

     源代码如下

    internal delegate void HelloCallBack(string name);
    class Program
    {
        static void Main(string[] args)
        {
            HelloCallBack helloShow = new HelloCallBack(ShowName);
            helloShow("hello");
        }

        static void ShowName(string name)
        {
            Console.WriteLine(name);
            Console.ReadLine();
        }         
    }

 中间代码如下

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  3
  .locals init ([0] class DelegateDemo.HelloCallBack helloShow)
  IL_0000:  nop
  IL_0001:  ldnull
  IL_0002:  ldftn      void DelegateDemo.Program::ShowName(string)
  IL_0008:  newobj     instance void DelegateDemo.HelloCallBack::.ctor(object,
                                                                       native int)
  IL_000d:  stloc.0
  IL_000e:  ldloc.0                                    //将索引 0 处的局部变量加载到计算堆栈上
  IL_000f:  ldstr      "hello"                        //把一个字符串常量装入堆栈
  IL_0014:  callvirt   instance void DelegateDemo.HelloCallBack::Invoke(string) //对对象调用后期绑定方法,并且将返回值推送到计算堆栈上

  IL_0019:  nop
  IL_001a:  ret
} // end of method Program::Mains

 

helloShow("hello") 等价于 helloShow.Invoke("hello");

完整的代码如下

    internal delegate void HelloCallBack(string name);
    class Program
    {
        static void Main(string[] args)
        {
            HelloCallBack helloShow = new HelloCallBack(ShowName);
            helloShow.Invoke("hello");
 Console.ReadLine();
 }

        static void ShowName(string name)
        {
            Console.WriteLine(name);
        }         
    }

 Invoke是怎么实现的呢,查看中间代码如下

.method public hidebysig newslot virtual 
        instance void  Invoke(string name) runtime managed
{
} // end of method HelloCallBack::Invoke

 runtime managed 表示此方法运行时有CLR处理,我推测类似于

            Delegate[] delegates = helloShow.GetInvocationList();
            for (int i = 0; i < delegates.Length; i++)
            {
                Delegate callback = delegates[i];
                MethodInfo method = callback.Method;
                method.Invoke(helloShow.Target, new object[] { "hello" });
            }

 4、委托链

委托链是委托对象的集合,利用它,可以调用委托的所有方法

Delegate有两个公共属性

Target  获取类实例,当前委托将对其调用实例方法。(静态方法访问空)

Method 获取委托所表示的方法。

多播委托的使用如下

namespace DelegateDemo
{
    internal delegate void HelloCallBack(string name);
    class Program
    {
        static void Main(string[] args)
        {
            HelloCallBack helloShow = new HelloCallBack(ShowName);
            helloShow += ShowCHName;
            helloShow.Invoke("hello");
            Console.ReadLine();
        }

        public static void ShowCHName(string name)
        {
            Console.WriteLine("你好:"+name);
        }    

        public static void ShowName(string name)
        {
            Console.WriteLine(name);
        }         
    }
}

 查看MulticastDelegate源码可知,委托链保存在private object _invocationList;

HelloCallBack helloShow = new HelloCallBack(ShowName);

_invocationList被初始化成object[],数组的第一个元素为new HelloCallBack(ShowName)委托

 helloShow += ShowCHName;

 +使用了运算符重载,它实际调用的是Delegate的Combine,-号实际调用的是Delegate的Remove




 

posted @ 2011-11-14 15:17  虎头  阅读(4518)  评论(0编辑  收藏  举报