CLR系列:浅析委托

前几天写了一篇文章:CLR系列:浅析泛型的本质,感谢大家的支持,看到一位读者说,泛型已经是2.0的技术,现在都是3.5了,研究他已经没必要,落后了。在这里,我想对大家说,无论是泛型,还是反射,还有今天我要讲的委托都是组成.NET开发的重要技术,什么时候研究都不算晚,都是有必要的。无论什么新的技术都是建立在老技术上面的。只要老的技术了解了,对与新技术也就不算很难了,再说不管过去的2.0,还是现在的3.5,还是将来的4.0,这些都是一样的。因此个人认为还是有研究的必要。废话多了,下面就对委托进行讲解,也许你能再这些老技术上面得到一个不同的认识。

先看看下面的例子:

 1     internal delegate void feedback();
 2     class Delegates
 3     {  
 5         public void callfeed(Delegates d)
 6         {
 7             feedback fb=null;
 8             fb += new feedback(d.callback1);
 9             fb += new feedback(callback2);
10             print(fb);
11 
12         }
13         public void print(feedback b)
14         {
15             if (b != null)
16                 b();
17         }
18         public void callback1()
19         {
20             Console.WriteLine("sss");
21         }
22         public static void callback2()
23         {
24             Console.WriteLine("ddd");
25         }
26     }

 这是个很普通的关于委托的例子。通过下面产生的IL代码来看看委托的基本工作原理

Code

 例子是实现一个多播委托,将几个方法绑定到一个委托变量,调用一个方法时,可以依次执行其绑定的所有方法。在代码里我们可以通过+=和-=操作符可以执行绑定和解除绑定的操作。但是编译器编译代码后生成的IL却不是这样的。通过IL代码:我们知道上面两种操作符分别被翻译成Delegate.Combine和Delegate.Remove。通过这两个方法来实现的。那么到底委托时个什么东西呢?我们来看看委托变量feedback的IL定义。

Code

在这里,我们发现.NET的委托变量都是继承System.MulticastDelegate类的,委托本质上还是个类。MulticastDelegate里维护着三个字段:

_target  system.oblect   当委托封装一个静态方法,为null,如果为实例方法,引用的是调用回调方法时要操作的对象。

_methodPtr system.InPtr  用来表示回调的方法。

_invocationList system.oblect 当为多播委托的时候可以引用一个委托数组。

如果大家对此还是将信将疑的话,请看下面的代码:

 1  public void callfeed(Delegates d)
 2         {
 3             feedback fb=null;
 4             fb += new feedback(d.callback1);
 5             Console.WriteLine(fb.Method.Name);
 6             Console.WriteLine(fb.Target.GetType());
 7             Console.WriteLine(fb.GetInvocationList().Length);
 8             fb += new feedback(callback2);
 9             Console.WriteLine(fb.Method.Name);
10             try
11             {
12                 Console.WriteLine(fb.Target.GetType());
13             }
14             catch{}
15             finally{
17                 Console.WriteLine(Nullable.Equals(fb.Target,null));
18             }
19             Console.WriteLine(fb.GetInvocationList().Length);
20             print(fb);
21 
22         }

 运行得出的结果,我们看看结果可以得出以上的结论了,GetInvocationList()时返回委托链的每个委托

1 callback1
2 DelegatesAndEvents.Delegates
3 1
4 callback2
5 True
6 2

在调用多播委托的时候,将按照委托列表的委托顺序而调用的。我们再把上面的代码改改:

 1         public void print(feedback b)
 2         {
 3             if (b != null)
 4             {
 5                 Delegate[] list = b.GetInvocationList();
 6                 foreach (feedback _b in list)
 7                     _b();
 8                 b();
 9             }
10         }

 运行得到的结果发现循环调用GetInvocationList()结果集与直接调用委托是一样的效果。在这里我们知道调用委托实际是通过Invoke()方法调用的。这里就很快的明白Invoke()方法内部其实是对GetInvocationList()的调用。而GetInvocationList()方法是根据你添加一个委托到委托链是添加一个的。因此保证了能顺序调用方法。但是这样的机制并不能保证每个方法都能得到回调,假如其中一个方法发生异常或等待很长时间的时候,就不会调用后面的方法,那么怎么解决这个问题呢?我们可以想以上自己对GetInvocationList()的集合去循环,处理以上的问题,保证每个方法得到调用。

下面我们将通过WINDBG+SOS调试看看以上的验证。

首先!load sos

然后使用!DumpHeap -type Delegates命令查看Delegates的所有实例:

 Address       MT     Size
0143d41c 00a79150       
12     
0143d428 00a79150       
12     
0143d434 00a79214       
32     
0143d454 00a79214       
32     
0143d48c 00a79214       
32     
total 
5 objects
Statistics:
      MT    Count    TotalSize 
Class Name
00a79150        
2           24 DelegatesAndEvents.Delegates
00a79214        
3           96 DelegatesAndEvents.feedback
Total 
5 objects

 我们可以看到Delegates一共生成了5个实例,其中最后三个是委托feedback的实例:然后我们看看第三个实例,也就是将所有的委托都加到委托链的时候在堆里到底都是些什么呢.

0:011> !dumpobj 0142f48c
Name: DelegatesAndEvents.feedback
MethodTable: 0127030c
EEClass: 00de5850
Size: 32(0x20) bytes
 (
D:\My Documents\DelegatesAndEvents\DelegatesAndEvents\bin\Debug\DelegatesAndEvents.exe)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
790fd0f0  40000ff        
4        System.Object  0 instance 0142f48c _target
7910ebc8  
4000100        8 ection.MethodBase  0 instance 00000000 _methodBase
791016bc  
4000101        c        System.IntPtr  1 instance  1201390 _methodPtr
791016bc  
4000102       10        System.IntPtr  1 instance  12702c8 _methodPtrAux
790fd0f0  400010c       
14        System.Object  0 instance 0142f474 _invocationList
791016bc  400010d       
18        System.IntPtr  1 instance        2 _invocationCount

  这里我们看看最后一句 _invocationCount为2,说明这个委托链表维护的是2个回调方法。

可能大家有很多人对委托有了一定的认识,但是我希望这篇文章能对大家有所帮助。让大家更清晰的认识委托。认识了委托,将有助你更好的理解匿名方法,Lambda表达式,LINQ。

欢迎大家批评指教。

posted on 2008-12-09 11:30  gjcn  阅读(2631)  评论(8编辑  收藏  举报

导航