Microsoft.Net框架程序设计学习笔记(26):委托揭秘

  让我们先看看以下示例代码:

  
    
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委托的签名是相同的。

你可能感兴趣的:(Microsoft)