• 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。

    欢迎大家批评指教。

    版权所有归"布衣软件工作者".未经容许不得转载.
  • 相关阅读:
    单向绑定和双向绑定
    Vue
    事件处理
    网关
    同时加载多个配置集
    Nacos Group方案
    DataID方案
    maven_provided说明
    C#大数据导入-SqlBulkCopy
    https://webyog.com/product/monyog/
  • 原文地址:https://www.cnblogs.com/gjcn/p/1350962.html
Copyright © 2020-2023  润新知