CLR系列:浅析委托
前几天写了一篇文章:CLR系列:浅析泛型的本质,感谢大家的支持,看到一位读者说,泛型已经是2.0的技术,现在都是3.5了,研究他已经没必要,落后了。在这里,我想对大家说,无论是泛型,还是反射,还有今天我要讲的委托都是组成.NET开发的重要技术,什么时候研究都不算晚,都是有必要的。无论什么新的技术都是建立在老技术上面的。只要老的技术了解了,对与新技术也就不算很难了,再说不管过去的2.0,还是现在的3.5,还是将来的4.0,这些都是一样的。因此个人认为还是有研究的必要。废话多了,下面就对委托进行讲解,也许你能再这些老技术上面得到一个不同的认识。
先看看下面的例子:
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代码来看看委托的基本工作原理
例子是实现一个多播委托,将几个方法绑定到一个委托变量,调用一个方法时,可以依次执行其绑定的所有方法。在代码里我们可以通过+=和-=操作符可以执行绑定和解除绑定的操作。但是编译器编译代码后生成的IL却不是这样的。通过IL代码:我们知道上面两种操作符分别被翻译成Delegate.Combine和Delegate.Remove。通过这两个方法来实现的。那么到底委托时个什么东西呢?我们来看看委托变量feedback的IL定义。
在这里,我们发现.NET的委托变量都是继承System.MulticastDelegate类的,委托本质上还是个类。MulticastDelegate里维护着三个字段:
_target system.oblect 当委托封装一个静态方法,为null,如果为实例方法,引用的是调用回调方法时要操作的对象。
_methodPtr system.InPtr 用来表示回调的方法。
_invocationList system.oblect 当为多播委托的时候可以引用一个委托数组。
如果大家对此还是将信将疑的话,请看下面的代码:
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()时返回委托链的每个委托:
2 DelegatesAndEvents.Delegates
3 1
4 callback2
5 True
6 2
在调用多播委托的时候,将按照委托列表的委托顺序而调用的。我们再把上面的代码改改:
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的所有实例:
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的实例:然后我们看看第三个实例,也就是将所有的委托都加到委托链的时候在堆里到底都是些什么呢.
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。
欢迎大家批评指教。