代码改变世界

深入理解Delegate(委托)

2012-02-09 16:26  jiejiep  阅读(1391)  评论(2编辑  收藏  举报
  委托,这个在 net framework 中引入的新概念,实际上在 c/c++ 中能够找到原型,即函数指针,如 c 运行时的 qsort 函数,该函数获取指向一个回调函数的指针,以便对数组中的元素进行排序。众所周知,指针变量中存放的是一个地址,这个地址不包含回调函数的任何信息,比如参数个数、参数类型、返回值,这样编译器就无法对传入的参数是否合法进行判断,如果非法,只能在运行时报错。也就是说,非托管C/C++回调函数不是类型安全的。委托,则能保证回调函数的类型安全,如果传入的回调函数不合法,编译就不会通过。
  委托的声明:
public delegate void DemoDelegate(string strName, int iAge);
  
  首先,我们要明确一个概念:定义一个委托就是定义一个类,一类函数,有相同的签名的函数。凡是可以定义类的地方都可以定义委托(个人理解,说法可能不太专业)。接下来,我们来分析一下上面这行代码的各个组成部分。我们既然说委托表示的是一类具有相同签名的函数,那么上面的 void 就标识这类函数的返回类型为 void, 具有两个参数,一个是 String 类型的,一个是 Int32 类型的,不考虑函数是静态方法(类方法)还是实例方法,也不考虑方法的访问级别。
  然后,我们来看一下如何调用的问题。
        static void Main(string[] args)
{
DemoDelegate fbStatic = new DemoDelegate(Program.StaticPrintInfo);
fbStatic("jie_Peng", 23);

DemoDelegate fbInstance = new DemoDelegate(new Program().PrintInfo);
fbInstance.Invoke("jie_Peng", 24);
}

public delegate void DemoDelegate(string strName, int iAge);

private static void StaticPrintInfo(string myName, int myAge)
{
Console.WriteLine(string.Format("I am from Static Method. My name is {0}, and I am {1} years old !", myName, myAge));
}

private void PrintInfo(string strName, int iAge)
{
Console.WriteLine(string.Format("I am from object method ! My name is {0}, and I am {1} years old !", strName, iAge));
}

输出如下:

观察图中的代码,我们可以发现,委托的实例对象,就相当于是方法的一个包装器,使方法能够通过包装器来间接的回调。我们发现,回调函数的调用,我们使用了两种不同的方法,他们真的不同吗?实际上,他们是相同的。编译器会将 fbStatic("jie_Peng",23) 编译为 fbStatic.Invoke("jie_Peng", 23)

  接下来,我们来讨论一下编译器对定义的委托 DemoDelegate 都做了些什么处理。

我们发现,编译器为我们生成了一个类 DemoDelegate ,该类继承自 System.MulticastDelegate (所有的委托类型都派生自该类),而后者又继承自 System.Delegate(继承自 System.Object) 类。编译器为我们生成了4个方法:一个构造器InvokeBeginInvokeEndInvoke

  我们先来分析构造函数: public DemoDelegate(Object obj, IntPtr method) 。你可能就疑惑了,我们的初始化委托对象的语句分明就是  DemoDelegate fbStatic = new DemoDelegate(Program.StaticPrintInfo); 怎么和类 DemoDelegate 构造函数不匹配呢?这如何又能通过编译呢?在回答这个问题之前,我们先来介绍一下所有委托的父类 System.MulticastDelegate 中的三个私有成员:object _target , IntPtr _methodPtr , object _invocationList 。 接下来我们对这3个私有成员一一做出解释。

_target :当委托对象包装一个静态方法时,这个字段为 null 。当委托对象包装一个实例方法时,这个字段引用的是回调方法要操作的对象。也就是说,这个字段指出要传给实例方法的隐式参数this的值(该对象指向调用回调函数的实例对象)。

_methodPtr一个内部整数值,CLR用它标识要回调的方法。

_invocationList :该字段通常为null。构造委托链时,它可以引用一个委托数组。

  好了,我们现在来分析类 DemoDelegate 的构造函数。该构造函数有两个参数,objectIntPtr ,你们是不是已经猜到这2个参数的意思了?对的,当编译器知道要初始化的对象是委托对象时,所以会分析源代码,来确定引用的是哪个对象和哪个方法。然后在构造函数内将对象的值赋值 obj 赋值给 _target , 将 method 赋值给 _methodPtr  ,并将 _invovationList 也置为 null ,对于传入的静态方法,则将 _target 置为 null 如下图,我们来看看 fbStatic 和 fbInstance 对应的私有字段的赋值情况。

  讲到这里,你可能在想,那我们该如何获取到这些私有字段呢?这个 FCL 已经为我们提供了访问的方式,即 System.Delegate 的两个属性:TargetMethodTarget 返回的是 _target 字段的值,而 Method 返回的是封装了 _methodPtr 后的 System.Reflection.MethodInfo 对象。