• 委托内部机制


      小弟第一次写文章,还请大侠亲拍指正。(蛋疼的Chrome崩溃了 重新发一遍)

      我们通俗点讲委托就是在一个方法中挖了一个坑,这个坑留着调用该方法的时候来填坑(给该委托赋值),这样子就可以做到需求留着调用者来完成,实现代码重用。网上有个比较经典的例子,烧水的一个程序水到达一定的温度的时候,水壶可能鸣笛报警,可能停止烧水,或者其他的可能。这件事是制造水壶的人不可预知,所以在这里挖了一坑,留着谁调用该烧水程序的人来填坑。当你填上鸣笛报警,水温到那个温度的时候就会鸣笛报警,当你填上停止烧水,到那个温度的时候就会停止烧水(其实这地方用事件更好,这里面说只是方便理解)。、

    附上代码:

      

     1   class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5 
     6             MyDel del = new MyDel(M1);
     7             del += M2;
     8             del += M3;
     9             Delegate[] dels = del.GetInvocationList();
    10             foreach (var item in dels)
    11             {
    12                 var my = item as MyDel;
    13                 Console.WriteLine(my());
    14             }
    15             Console.WriteLine(del()); 
    16             Console.Read();
    17         }
    18         public static int M1()
    19         {
    20             return 1;
    21         }
    22         public static int M2()
    23         {
    24             return 2;
    25 
    26         }
    27         public static int M3()
    28         {
    29             return 3;
    30         }
    31        
    32     }

      首先委托是一个数据类型,跟class,enum,struct一样。定义的委托的时候我们可以这样定义:public delegate int MyDel();其中delegate是定义委托的关键字,int是返回类型,MyDel是委托类型名,括号中可以放你需要的参数(这里面我给的无参)。

    然后定义委托变量,public MyDel del=M1;其中M1是

    public static int M1()

            {

                return 1;

            }

    这样就在我们就可以在Main方法中del()就可以调用该方法。但是当我们实现多播委托的时候如果其中有几个方法例如

     1 public static int M1()
     2         {
     3             return 1;
     4         }
     5 
     6         public static int M2()
     7         {
     8 
     9             return 2;
    10 
    11         }
    12 
    13         public static int M3()
    14         {
    15 
    16             return 3;
    17 
    18         }

    我们就可以MyDel del = new MyDel(M1);

               del+= M2;

               del+= M3;

    给委托进行赋值。这样调用Console.WriteLine(del())时候我们得到的结果只能是3。那我们怎么得到所有的返回值?

      我们可以通过Delegate[] dels = del.GetInvocationList();得到Delegate数组。这样我们通过

    foreach (var item in dels)
                {
    
                    var my = item as MyDel;
    
                    Console.WriteLine(my());
    
                }

    可以得到每个的返回值。那大家就会问为什么可以这样的呢?

           贴上Main方法(前面已经附上了):

     1 static voidMain(string[] args)
     2 
     3         {
     4 
     5             MyDel del = new MyDel(M1);
     6 
     7             del += M2;
     8 
     9             del += M3;
    10 
    11             Delegate[] dels = del.GetInvocationList();
    12 
    13             foreach (var item in dels)
    14 
    15             {
    16 
    17                 var my = item as MyDel;
    18 
    19                 Console.WriteLine(my());
    20 
    21             }
    22 
    23             Console.WriteLine(del());
    24 
    25             Console.Read();
    26 
    27         }

      我们通过反编译可以看到下面的代码:

     1 private static void Main(string[] args)
     2     {
     3         MyDel del = new MyDel(Program.M1);
     4         del = (MyDel) Delegate.Combine(del, new MyDel(Program.M2));
     5         del = (MyDel) Delegate.Combine(del, new MyDel(Program.M3));
     6         Delegate[] dels = del.GetInvocationList();
     7         foreach (Delegate item in dels)
     8         {
     9             Console.WriteLine((item as MyDel)());
    10         }
    11         Console.WriteLine(del());
    12         Console.Read();
    13     }

      首先我们得理解我们写的所有的委托都是继承自MulticastDelegate(抽象类),而MulticastDelegate又继承自Delegate类。在MulticastDelegate中有个重要的字段:_invocationList,Delegate中有两个重要字段是_target和_methodPtr。

      当我们MyDel del = new MyDel(M1);的时候首先他的该委托变量会存放_methodPtr中,而_target指向的是该类型的实例地址,他指向的是Program,_invocationList就是一个空的Delegate数组(null)。

         我们通过+=实现委托链的时候,MulticastDelegate会重写Delegate类中的CombineImpl方法(我截取一部分,这里以del+=M2;为基准):

     1 //follow代表的是M2
     2 protected sealed override Delegate CombineImpl(Delegate follow){
     3 object[] objArray;
     4 int num2;
     5 //假如follow(M2)为null的时候返回自己
     6     if (follow == null)
     7     {
     8         return this;
     9 }
    10 //判断是否类型相同
    11     if (!Delegate.InternalEqualTypes(this, follow))
    12     {
    13         throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
    14 }
    15 //将follow转为MulticastDelegate类型,刚刚已经提过如果是单播委托//的时候invocationList为null
    16     MulticastDelegate o = (MulticastDelegate) follow;
    17 int num = 1;
    18 //这时候objArray2就为 null
    19     object[] objArray2 = o._invocationList as object[];
    20     if (objArray2 != null)
    21     {
    22         num = (int) o._invocationCount;
    23 }
    24 //将自己的invocationList转换为数组,同上得到的也是null
    25     object[] objArray3 = this._invocationList as object[];
    26     if (objArray3 == null)
    27 {
    28 //得到num2结果为2
    29         num2 = 1 + num;
    30         objArray = new object[num2];
    31     //将自己放到objArray[0]中
    32         objArray[0] = this;
    33     //将添加的委托放到objArray[1]中
    34         if (objArray2 == null)
    35         {
    36             objArray[1] = o;
    37         }
    38     //如果增加的委托变量也是多播委托才会走这步
    39         else
    40         {
    41             for (int i = 0; i < num; i++)
    42             {
    43                 objArray[1 + i] = objArray2[i];
    44             }
    45         }
    46     //返回一个新的委托这里面返回的invocationList存放了两个Delegate数组,数组中每一个代表委托链中的一个委托实例下面的代码
    47         return this.NewMulticastDelegate(objArray, num2);
    48 }..(后面代码不在写)}
    49 //返回一个新的委托
    50     internal MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
    51 {
    52     MulticastDelegate delegate2 = Delegate.InternalAllocLike(this);
    53     if (thisIsMultiCastAlready)
    54     {
    55         delegate2._methodPtr = base._methodPtr;
    56         delegate2._methodPtrAux = base._methodPtrAux;
    57     }
    58     else
    59     {
    60         delegate2._methodPtr = base.GetMulticastInvoke();
    61         delegate2._methodPtrAux = base.GetInvokeMethod();
    62     }
    63     delegate2._target = delegate2;
    64     delegate2._invocationList = invocationList;
    65     delegate2._invocationCount = (IntPtr) invocationCount;
    66     return delegate2;
    67 }

      所以我们可以通过下面图来理解单播委托与多播委托:

      这边提一点:单播委托的时候_invocationList为null,而多播委托的时候,_invocationList是一个Delegate[],数组中每一个成员就是一个委托实例。

      这里我们的注意的一点是委托跟字符串具有一样的特性:不可变性。我们刚开始定义委托变量MyDel del = new MyDel(M1);堆内存中分配了一块地址当del+=M2;的时候堆内存为M2分配了一块内存地址,而堆为del重写分配了一块地址用于存放del,栈中重新指向了另外一块内存地址。所以一开始的del是没有改变。这时候一开始堆内存的del(M1)跟M2就可以被垃圾回收了。

      附上赵晓虎的图:

      扯到这不得不提类堆内存的存放。借用我们马伦的画的一张图:

      

      当程序集(dll,exe)加载的时候,在appDomain堆内存中就有一块内存空间用于存放该类型的实例,例如Person类型的实例。当我们Person person=new Person()的时候,在堆内存中一块内存地址由于存放person的变量的空间。他们都会有对象空间,类型指针,同步索引块。我们当前创建的person就会指向person实例的地址,Person就会指向Person类型的实例地址。当前创建的person的类型指针会指向Person类型地址。而Person类型实例中的类型指针会指向自己。

           在图中DelDefine del=new DelDefine(person.Show)其中person实例就指向了target,而target就指向了person实例空间地址。methodPtr指向了Show方法。这样我们del()的时候,就是通过person实例调用方法。

      好了就说这么多了,欢迎大侠们亲拍指正!!!

  • 相关阅读:
    PAT 1008--------数组元素的循环右移,你需要记住的
    PAT1049-----枚举法,找规律题,注意降低时间复杂度
    PAT1048----你需要了解并记住的解题思路
    C++中几个输入函数的用法和区别(cin、cin.get()、cin.getline()、getline()、gets()、getchar()))
    PAT1040----关于数学题目的解法新思路值得借鉴,字符的配对
    PAT1029-----介绍字符串的解题思路和部分知识点
    PAT1027-----等差数列的问题或数学问题
    PAT1026----四舍五入的思路,%2d的一些知识
    / 已阅 /PAT1017-------高精度计算,问题的所有可能情况
    LeetCode 无重复字符的最长子串
  • 原文地址:https://www.cnblogs.com/sjr10/p/delegate_essence.html
Copyright © 2020-2023  润新知