你的位置:首页 > ASP.net教程

[ASP.net教程]Delegate背后的秘密


表面上看来使用delegate是一件很简单的事。

用delegate关键字定义,使用老套的new创建一个instance ,使用熟悉的方法调用写法调用,只不过不在是方法名,而是委托名。
但是在这背后CLR为我们做了很多。
 
当我们 写下下面这句话时
 
public delegate void FeedBack(Int32 val);
 
其实相当于 大概下面的代码(省略部分多线程相关代码)
 
pubic class FeedBack:MulticastDelegate
{
  public FeedBack(Object  object ,  IntPtr  methodPtr)
  {
   xxxx
  }
  public virtual void Invoke(int32 val);
}
 
首先delegate 就是一个class。这个class会自动继承MuticastDelegate .然后,这个class 的构造函数就如上面所示, 
我们先看第二个参数 methodPtr  可以理解为函数的指针,或者说函数的地址。委托在真正运行的时候,就要找到这个函数的地址,并运行这个函数。对,我们要先找到这个函数的地址,对于 static类型的函数,地址是确定的,而对于实例函数而言,内部的函数地址则是不定的,它需要先确定具体的某个实例, 这既是第一个参数的用途,具体实例的引用,对于static类型的函数,就会默认传入一个null。
其实,在基类 MutiCastDelegate中,有三个重要的非公开属性
 
_target 
_methodPtr
_invokationlist
 
一二两个属性 刚好对应我们构造函数传入的两个参数,第三个属性和委托链有关,默认为null,我们先不讨论。
 
也就是说:delegate 其实只是个wrapper ,包装着需要真正操作的对象 以及它的方法。
 


也就是说委托的构造函数 完成了 和具体真实对象以及真实方法的对应,而调用则是另一个方法 Invoke(int32 val)。这个方法会触发真实方法的调用。
 

关于委托链式。。。我就不深究了,因为实在比较懒。。
可以理解为 一个delegate数组一次存放着这些委托,然后调用的时候,内部通过foreach一个一个调用。。.也就造成了整个委托链中的方法都被调用的 “链式反应”。因为内部实现也就是数组,所以 具体调用次序是确定的,而不是随机的,根据的加入委托链中的顺序会依次调用这些方法。
如何拼成一个委托链,有两种写法
 
在早期版本中 通过 Delegate.Combine 静态方法来拼接链,(每次拼接要记得赋给原先的链)。就像现实生活中装链条一样,有两个参数,第一个参数是 已装的部分链,第二个参数是待装的。(链子的内部是依靠数组实现的)
 
第二种写法,是C#为了更好的辅助delegate而 自动对+= 和-=这两个操作符提供了重载。也就是说delegate实例之间可以简单的通过
+=和-=来拼接。这无疑相当大的简化了 委托链。(至于怎么重载的,因为懒。。就不模拟了。)
 
拼完委托链之后,只需要直接调用委托一样调用委托链即可,委托内部的算法会依次调用每个拼接的元素。但有一个缺憾就是,如果委托是带有返回值的,那么只能返回最后一个委托执行的返回值前面所有委托的返回值都会被丢弃。
有什么办法呢,如下。



委托链的高级调用
 
如上所说,如果直接invoke委托链的话,那么只有链尾的返回值会输出出来,并且简单直接的链式调用还有巨大的隐患,如果其中的某个delegate抛出异常,那整个链都会卡住   。如果想要的每一个委托的返回值,就不能用上面的方法。
 


委托与泛型
 
我们先定义一个委托
 
public void FeedBack(Int32 value);
 
然后我们又突然需要另一个委托。。o(╯□╰)o
 
public void FeedBack(string value)
 
可以这样来定义吗? ,绝对不可以!!委托的本质是class 所以你上面的做法只是重复定义了同名的类罢了。
 
委托可以融入泛型来更加强大   ,看下面的完整实例
 
   
 public delegate void FeedBack <T>(T para);  public delegate void FeedBack<Tint,Tstr>(Tint para1,Tstr para2);  class 委托可以有泛型吗  {    public static void Main(string[] args)    {            FeedBack<Int32 > fb = new FeedBack<int >(FeedBackInt);      FeedBack<String > fb2 = new FeedBack<string >(FeedbackString);      FeedBack<int ,string > fb3 = new FeedBack<int , string >(FeedbackIntString);      fb.Invoke(2);      fb2.Invoke( "haha");      fb3.Invoke(60,"haha" );      Console.ReadKey();    }     public static void FeedBackInt(Int32 val)    {      Console.WriteLine(val);    }    public static void FeedbackString(String str)    {      Console.WriteLine(str);    }    public static void FeedbackIntString(int val,string str)    {      Console.WriteLine(val+":" +str);    }  }}

 



 
不知道看到这里,你心里会不会隐隐约约有一种想法。。。。那就是微软所想到的:通过泛型可以把你编程几乎会用到的所有可能的委托囊括!!!!
他们确实这样做了。。这就是FCL 拥有的自建委托。不带返回值的统称  Action  带返回值的统称  Func. 自带委托如下,参数最多
上限16个。再多的话,这个方法本身就很有问题了。
 

 
以及

 
微软建议,在需要使用委托的时候就是用内建的这些委托,而不要再去自己创建委托类型了。那我们就遵守这个惯例吧。
当然 也有可能这些泛型不符合你需要的委托。 比如你需要这个
delegate void bar(ref  int32 z);  ,或者说你的泛型委托需要一些约束。那就得自己定义委托了。。
 
 

最常见的delegate的用途应该是webform中的事件处理机制。
我们常常会见到这样的写法:
button1.Click += button1_Click;
 
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
 
在学习的时候竟然很少有人对这种写法感到诧异。。 这是C# 提供的语法糖。
正常的写法是 button1.Click += new EventHandler(button1_Click);
现在认识么? 就是简单的拼接 delegate 链而已╮(╯_╰)╭。  上面的语法糖是委托里的第一种语法糖。
在需要委托实例的地方 不再需要委托实例,直接提供委托要包含的的函数名即可。
 
(events 是更安全的delegate .实际上是使用自建的EventHandler委托,然后,每次触发不同控件的时候,会通过EventHandler(sender,e)) 这个委托来触发实际的方法。
 
ok 委托先到这里。