让我们先看看以下示例代码:
using System;
using System.Collections.Generic;
using System.Text;
namespace DelegateTest
{
// 定义一个委托
public delegate void TestDelegate( int [] array);
class Program
{
// 定义一个数组,用于传递委托参数
static int [] array = new int [] { 1 , 2 , 3 , 4 , 5 };
// 调用委托
static void Load(TestDelegate td)
{
if (td != null )
{
td(array);
}
}
static void Main( string [] args)
{
// 使用静态方法构造一个委托
TestDelegate td1 = new TestDelegate(Sum);
Load(td1);
// 使用实例方法构造一个委托
TestClass tc = new TestClass();
TestDelegate td2 = new TestDelegate(tc.Length);
td2 += new TestDelegate(tc.Display);
Load(td2);
Console.ReadKey();
}
// 静态方法
static void Sum( int [] array)
{
int sum = 0 ;
foreach ( int i in array)
{
sum += i;
}
Console.WriteLine( " 该数组的和为: " + sum.ToString());
}
}
// 测试类
class TestClass
{
// 实例方法,用于委托调用
public void Length( int [] array)
{
Console.WriteLine( " 该数组的长度为: " + array.Length);
}
// 实例方法,用于委托调用
public void Display( int [] array)
{
foreach ( int i in array)
{
Console.WriteLine(i);
}
}
}
}
这段代码的输出为:
该数组的和为:15
该数组的长度为:5
1
2
3
4
5
委托的定义
现在让我们来看看定义委托时,编译器都做了些什么?
public delegate void TestDelegate(int[] array);
当编译器遇到这段代码时,它会产生如下所示的一个完整类定义:
public class TestDelegate : System.MulticastDelegate
{
// 构造器
public TestDelegate( object target, int methodPtr);
// 下面的方法和源代码中指定的原型一样
public void virtual Invoke( int [] array);
// 下面两个方法允许我们对委托进行异步回调
public virtual IAsyncResult BeginInvoke( int [] array, AsyncCallback callback, object object );
public virtual void EndInvoke(IAsyncResult result);
}
由这段代码可以看出,委托本身就是一个继承自System.MulticastDelegate的类。
在MulticastDelegate类中,有3个私有字段值得我们关注:
_target:System.Object类型,指向回调用函数被调用时应该被操作的对象。该字段用于实例方法的回调。
_methodPtr:System.Int32类型,一个内部的整数值,主要用于表示指针或句柄,CLR用它来标识回调方法。
_prev:System.MulticastDelegate,指向另一个委托对象。该字段通常为null。
所有的委托都有一个构造器,且该构造器接受两个参数:一个对象引用和一个指向回调方法的整数。但在示例代码中,创建一个委托实例时,我们传入的是静态方法Sum或实例方法tc.Length,与构造器的参数不符啊?怎么回事呢?
实际上编译器知道我们正在构造的是一个委托,它会通过分析源代码来确定我们引用的是哪个对象的哪个方法。其中,对象的引用会被传递给target参数,而一个特殊的标识方法的Int32值(由MethodDef或MethodRef元数据标记获得)会被传递给methodPtr参数。对于静态方法而言,null会被传递给target参数。在构造器内部,这两个参数会被保存在相应的私有字段中。
而在构造器中_prev字段设置为null,该字段用于创建一个MulticastDelegate对象的链表。
每个委托对象实际上是对方法及其调用时操作的对象的一个封装。MulticastDelegate类定义了两个只读公共实例属性:Target、Method。Target属性返回一个方法回调时操作的对象引用。如果是静态方法,Target将返回null。Method属性返回一个标识回调方法的System.Reflection.MethodInfo对象。
委托的调用
委托调用代码:
td(array);
看起来我们在调用一个名为td的函数,且传递给了它1个参数。但实际上并没有什么td函数,因为编译器知道td是一个指向委托对象的变量,所以它会产生代码来调用该委托对象的Invoke方法。也就是说,编译器产生的代码类似如下代码:
td.Invoke(array);
其实我们亦可以显式调用以上代码执行委托。
当Invoke被调用时,它使用_target和_methodPtr两个私有字段来在指定的对象上调用期望的方法。注意Invoke方法的签名与TestDelegate委托的签名是相同的。