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代码来看看委托的基本工作原理

ContractedBlock.gif ExpandedBlockStart.gif Code
 1   IL_0005:  ldftn      instance void DelegatesAndEvents.Delegates::callback1()
 2   IL_000b:  newobj     instance void DelegatesAndEvents.Delegates/feedback::.ctor(object,
 3                                                                                   native int)
 4   IL_0010:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
 5                                                                                           class [mscorlib]System.Delegate)
 6   IL_0015:  castclass  DelegatesAndEvents.Delegates/feedback
 7   IL_001a:  stloc.0
 8   IL_001b:  ldloc.0
 9   IL_001c:  callvirt   instance class [mscorlib]System.Reflection.MethodInfo [mscorlib]System.Delegate::get_Method()
10   IL_0021:  callvirt   instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
11   IL_0026:  call       void [mscorlib]System.Console::WriteLine(string)
12   IL_002b:  nop
13   IL_002c:  ldloc.0
14   IL_002d:  callvirt   instance object [mscorlib]System.Delegate::get_Target()
15   IL_0032:  callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
16   IL_0037:  call       void [mscorlib]System.Console::WriteLine(object)
17   IL_003c:  nop
18   IL_003d:  ldloc.0
19   IL_003e:  callvirt   instance class [mscorlib]System.Delegate[] [mscorlib]System.Delegate::GetInvocationList()
20   IL_0043:  ldlen
21   IL_0044:  conv.i4
22   IL_0045:  call       void [mscorlib]System.Console::WriteLine(int32)
23   IL_004a:  nop
24   IL_004b:  ldloc.0
25   IL_004c:  ldnull
26   IL_004d:  ldftn      void DelegatesAndEvents.Delegates::callback2()
27   IL_0053:  newobj     instance void DelegatesAndEvents.Delegates/feedback::.ctor(object,
28                                                                                   native int)
29   IL_0058:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
30                                                                                           class [mscorlib]System.Delegate)
31   IL_005d:  castclass  DelegatesAndEvents.Delegates/feedback
32   IL_0062:  stloc.0
33   IL_0063:  ldloc.0
34   IL_0064:  callvirt   instance class [mscorlib]System.Reflection.MethodInfo [mscorlib]System.Delegate::get_Method()
35   IL_0069:  callvirt   instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
36   IL_006e:  call       void [mscorlib]System.Console::WriteLine(string

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

ContractedBlock.gif ExpandedBlockStart.gif Code
 1 .class auto ansi sealed nested assembly feedback
 2     extends [mscorlib]System.MulticastDelegate
 3 {
 4     .method public hidebysig specialname rtspecialname instance void .ctor(object 'object'native int 'method') runtime managed
 5     {
 6     }
 7 
 8     .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
 9     {
10     }
11 
12     .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
13     {
14     }
15 
16     .method public hidebysig newslot virtual instance void Invoke() runtime managed
17     {
18     }
19 
20 }
21 

在这里,我们发现.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。

欢迎大家批评指教。

转载于:https://www.cnblogs.com/gjcn/archive/2008/12/09/1350962.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值