委托内部机制

  小弟第一次写文章,还请大侠亲拍指正。(蛋疼的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实例调用方法。

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

posted @ 2012-08-17 23:44  sjR10  阅读(651)  评论(3编辑  收藏  举报