委托是什么

目前大部分文章关注是如何使用委托?为什么要使用委托?
却很少关注委托是什么?委托是如何工作的?明白这两个问题能帮助我们更好的理解使用委托。
本文的内容 就是针对这两个问题。

先看一个最简单的例子

 1     class Program
 2     {
 3         delegate void TestDelegate(int val);
 4         static void Main(string[] args)
 5         {
 6             TestDelegate Dele = new TestDelegate(Fun1);
 7             Dele += new Program().Fun2;
 8 
 9             Dele(2);
10 
11             Console.ReadKey();
12         }
13 
14         static void Fun1(int a)
15         {
16             Console.Write(a);
17         }
18         void Fun2(int b)
19         {
20             Console.Write(b);
21         }
22     }

这是我们看到的代码,对于委托只有一句:

 delegate void TestDelegate(int val);

或许不太好理解 委托到底是什么。

那么我们看IL代码,图1:

 

我们可以看到
  命名空间 MyProject 下包含 类型 MyProject.Program

类型MyProject.Program 下分别包含:

一个类型 TestDelegate,正是我们声明的委托。
构造函数:.ctor
静态方法:Fun1
实例方法:Fun2
程序入口:Main 

由此,我们可以知道 我们声明的委托TestDelegate是被编译成类型的。
然后在看其内部信息:
  继承自System.MulticastDelegate
  构造函数:.ctor
  异步方法:BeginInvoke,EndInvoke,
  常规调用方法:Invoke

    其中,System.MulticastDelegate 继承自 System.Delegate。

理清上面的关系,并且把各继承类中主要成员提取出来,于是我们上面的代码实际是这个样子:

 1 class Program
 2     {
 3         /// <summary>
 4         /// 委托类型,实际上System.Delegate提供了很多成员,这里只列出主要的成员。
 5         /// </summary>
 6         public sealed class TestDelegate
 7         {
 8             private object _invocationList;//Obiect类型,System.MulticastDelegate成员,委托列表,无委托列表时为null,创建委托列表时值为 Delegate[]
 9             private object _target;//Object类型,System.Delegate成员,当以实例方法创建委托时,保存该实例方法的对象。当以静态方法创建委托时,指向当前委托对象.
10             private System.IntPtr _methodPtr;//IntPtr类型,System.Delegate成员,当以实例方法创建委托时,保存该实例的方法引用,运行时是该方法的内存地址。
11             private System.IntPtr _methodPtrAux;//IntPtr类型,System.Delegate成员,当以静态方法创建委托时,保存静态的方法引用,运行时是该方法的内存地址。
12             public System.Reflection.MethodInfo Method;//只读属性,返回System.Reflection.MethodInfo对象,其中包含_methodPtr或_methodPtrAux指向的方法(即注册委托的方法)的相关信息。
13             public object Target;//只读属性,实例方法创建委托 返回_target,静态方法创建委托 返回null,
14             ///以下主要方法的实现以文字描述,也方便理解。本已写了部分伪代码,但有些操作是编译器实现的,伪代码也不好写。所以文字描述。
15             
16             /// <summary>
17             /// 构造函数,创建委托实例            
18             /// </summary>
19             /// <param name="target"></param>
20             /// <param name="method"></param>
21             protected TestDelegate(object target, string method) 
22             {
23                 //委托类的构造函数接受两个参数,但实际上我们创建的时候只传递了一个方法引用,为什么?        
24                 //实际上编译器 会分析我们传入的参数,将类型的对象引用传递给target,方法引用传递给method.
25                 初始化_invocationList==null;
26         
27                 当为实例方法时:
28                     传递target 给_target
29                     传递method给_methodPtr
30         
31                 当为静态方法时:
32                     传递当前委托对象给_target,但此时访问属性Target时,返回null
33                     method传递给_methodPtrAux    
34             }
35             /// <summary>
36             /// 添加委托
37             /// </summary>
38             /// <param name="a"></param>
39             /// <param name="b"></param>
40             /// <returns></returns>
41             public static Delegate Combine(Delegate a, Delegate b) 
42             {
43                 如果 a 和b 都为null ,抛异常
44                 如果a==null,返回b,b==null,返回a
45                 否则,合并a和b的委托列表(_invocationList),传递给b,返回b ;合并后,a委托列表在前,b委托列表在后           
46             }   
47             /// <summary>
48             /// 删除
49             /// </summary>
50             /// <param name="source"></param>
51             /// <param name="value"></param>
52             /// <returns></returns>
53             public static Delegate Remove(Delegate source, Delegate value)
54             {
55                 获取source._invocationList
56         
57                 如果source._invocationList 中包含value._invocationList
58                     从source._invocationList中移除 value._invocationList
59                     返回source
60         
61                 如果value==null 或 source._invocationList 中不包含value._invocationList
62                     返回source
63         
64                 如果source==null 或 source._invocationList ==value._invocationList 
65                     返回null
66             }
67             /// <summary>
68             /// 调用
69             /// </summary>
70             /// <param name="value"></param>
71             public void Invoke(int value)
72             {
73                 如果_invocationList为null,执行 _methodPtr.Invoke(_target,value)
74                 否则,遍历_invocationList(其值为Delegate[]),调用每一个委托
75             }
76         }
77         static void Main(string[] args)
78         {
79             TestDelegate Dele = new TestDelegate(Fun1);//调用构造函数,Fun1为静态方法,此时 Dele._target指向Dele自身
80             Dele += new Program().Fun2;//Fun2为实例方法,此时此时 Dele._target指向new Program()对象
81             //对于这一步的+=操作的具体步骤是:(注:编译器自动对委托实例重载了+=,-=操作,-=同理)
82             //1、 new Program(),并获取该对象Fun2方法的引用;静态方法时,直接获取方法引用。
83             //2、 new TestDelegate(),传入第一步方法引用为构造函数参数。
84             //3、 调用Combine方法。参数分别为Dele和第二步的委托对象。
85             
86             Dele(2);//调用Invoke方法
87             Console.ReadKey();
88         }
89         static void Fun1(int a)
90         {
91             Console.Write(a);
92         }
93         void Fun2(int b)
94         {
95             Console.Write(b);
96         }
97     }

 

委托的实质是一个类,其内部 维护了注册方法的 类型引用,方法引用及本身的委托列表等成员。
并提供了构造,添加,删除,调用等方法。最大的特色是可以对按顺序 调用 委托列表的中注册方法。

 

然后再来看事件
在上部分代码基础上添加事件。

 1     class Program
 2     {
 3         public delegate void TestDelegate(int val);
 4         public event TestDelegate TestEvent;
 5         static void Main(string[] args)
 6         {
 7             Program p = new Program();
 8             p.TestEvent += p.Fun2;
 9             p.TestEvent += Program.Fun1;
10             p.TestEvent(3);
11             Console.ReadKey();
12         }
13         static void Fun1(int a)
14         {
15             Console.Write(a);
16         }
17         public void Fun2(int b)
18         {
19             Console.Write(b);
20         }
21     }

直接看IL

主要成员:

1、名为TestEvent的私有TestDelegate对象
2、添加事件方法:add_TestEvent(TestDelegate value),参数为TestDelegate类型
3、删除事件方法:remove_TestEvent(TestDelegate value),参数为TestDelegate类型

 

对于添加操作TestEvent+=Fun2实际会做以下操作(删除同理):
1、获取Fun2引用(同委托获取引用)。
2、new TestDelegate(),并传入第一步引用为参数。
3、调用add_TestEvent方法,参数为上一步创建的TestDelegate实例。
4、在add_TestEvent方法内部,通过调用System.Delegate.Combine(Delegate a, Delegate b)方法,将第二步对象加入TestEvent对象委托列表 

 

在上述实例中就是在 Program对象 内部提前创建了一个私有TestDelegate委托对象TestEvent,并对其提供了 添加和删除 TestDelegate对象的方法。

事件的添加,删除,调用就是对TestEvent对象的添加,删除,调用。

可以看出 所谓事件只是对委托做了简单的包装。其本质依然是委托。

对于常用的标准事件的写法 public event EventHandler<EventArgs> TestEvent, 原理也如此,区别只是注册方法的参数不同而已。

 

posted @ 2014-08-19 13:55  摇啊摇啊摇  阅读(1621)  评论(2编辑  收藏  举报