委托引入和本質


前言

雖然關於委托的文章園子中不勝枚舉,但是要充分的理解委托的概念並且靈活運用,個人覺得還是要由淺入深,逐步推進,最后再來研究它的實質,這樣才能達到事半功倍的效果,如果不信,請看下文,相信我所言非虛(當然也歡迎園友們拍磚和批評)!

概念

(1)用Delegate類表示委托,委托是一種數據結構,它引用靜態方法或引用類實例及該類的實例方法。

(2)解述:委托聲明了一種類型,它用一種特定的參數以及返回類型來封裝方法。對於靜態方法,委托對象封裝要調用的方法。對於實例方法,委托對象同時封裝一個實例和該實例上的方法。如果有一個委托對象和一組適當的參數,則可以用這組參數來調用委托。

委托引入

平常我們就是寫一個方法,然后再傳入參數直接調用。如下:

 static void xsEat(string food)
 {
       Console.WriteLine("小三吃" + food);
 }

 xsEat("零食");  /*打印出小三吃零食*/

現在我們用委托來實現代碼如下:

    delegate void EatDelegate(string food);
    class Program
    {
        static void Main(string[] args)
        {
            EatDelegate xs = new EatDelegate(xsEat);
            xs("零食");
            Console.ReadKey();
        }

        static void xsEat(string food)
        {
            Console.WriteLine("小三吃" + food);
        }
    }

我們用關鍵字 delegate 定義一個EatDelegate委托,在聲明類的任何地方就能聲明委托,委托聲明的返回值和參數必須要和要調用的方法的簽名一致。接下來對委托進行實例化並傳入要代理的方法指針,此時實例化的對象 eat  就指向了方法 xs  ,再傳入參數,結果打印出小三吃零食。如果接下來有小紅(xh)和小明(xm)也過來吃零食,那我們也同樣寫這樣的方法,代碼如下:

 1     delegate void EatDelegate(string food);
 2     class Program
 3     {
 4         static void Main(string[] args)
 5         {
 6             EatDelegate xs = new EatDelegate(xsEat);
 7             xs("零食");
 8             EatDelegate xh = new EatDelegate(xhEat);
 9             xh("零食");
10             EatDelegate xm = new EatDelegate(xmEat);
11             xm("零食");
12             Console.ReadKey();
13         }
14 
15         static void xsEat(string food)
16         {
17             Console.WriteLine("小三吃" + food);
18         }
19 
20         static void xhEat(string food)
21         {
22             Console.WriteLine("小紅吃" + food);
23         }
24         static void xmEat(string food)
25         {
26             Console.WriteLine("小明吃" + food);
27         }
28     }

上述分別打印出小三吃零食、小紅吃零食、小明吃零食。看起來滿足了需求,但是我們仔細想想明明是吃零食為什么還要實例化三次呢?代碼能不能精簡了,當然有辦法,繼續是委托,這個時候就要用到 委托鏈 了。所以我們對控制台的代碼進行改寫如下:

        static void Main(string[] args)
        {
            EatDelegate xs = new EatDelegate(xsEat);        
            EatDelegate xh = new EatDelegate(xhEat);
            EatDelegate xm = new EatDelegate(xmEat);

            EatDelegate eat;
            eat = xs + xh + xm;
            eat("零食");

            Console.ReadKey();
        }

我們只需要把委托實例添加到委托鏈中即可同樣達到了上述的效果。此時小三、小紅和小明三個一起扎堆吃零食,后來陸陸續續的走了,通過這樣一段描述我們用強大的委托鏈來實現,代碼如下:

 1         static void Main(string[] args)
 2         {
 3             EatDelegate xs = new EatDelegate(xsEat);        
 4             EatDelegate xh = new EatDelegate(xhEat);
 5             EatDelegate xm = new EatDelegate(xmEat);
 6 
 7             EatDelegate eat;
 8             Console.WriteLine("小三、小紅和小明一起吃零食");
 9             eat = xs + xh + xm;
10             eat("零食");
11             Console.WriteLine("小三有約出去了,就剩下小紅和小明吃零食了");
12             eat -= xs;
13             eat("零食");
14             Console.WriteLine("小紅也走了,就剩下小明一個人吃零食了");
15             eat -= xh;
16             eat("零食");
17             Console.ReadKey();
18         }

結果運行如圖所示:

我們由此知道-=或者+=號來更容易的對委托鏈中的元素進行操作,這樣一來我們可以隨意而且是任意妄為的對其元素進行操作。對於上面的方法我們繼續進行精簡,由於方法比較簡單,微軟大大為我們提供了一個便捷的方式來實現那就是 匿名方法 。我們將代碼進行改寫如下:

    delegate void EatDelegate(string food);
    class Program
    {
        static void Main(string[] args)
        {

            EatDelegate eat = null;
            eat += delegate(string food) { Console.WriteLine("小三吃" + food);};
            eat += delegate(string food) { Console.WriteLine("小紅吃" + food); };
            eat += delegate(string food) { Console.WriteLine("小明吃" + food); };
            eat("零食");
            Console.ReadKey();
        }
    }

通過匿名方法使得我們能更加方便的創建委托和使用委托。 

通過上述我們不免心生疑問,它只能對靜態方法進行調用,難道不能對動態方法進行調用呢?同時上面的代碼未免有些冗余,也不能體現C#面向對象的思想!所以,鑒於此,我們對上述代碼繼續進行改寫,如下:

 1     public class Person
 2     {
 3         public string Name { get; set; }
 4 
 5         public Person(string name)
 6         {
 7             this.Name = name;
 8         }
 9 
10         public void Eat(string food)
11         {
12             Console.WriteLine(this.Name + "" + food);
13         }
14     }
15     delegate void EatDelegate(string food);
16     class Program
17     {
18         static void Main(string[] args)
19         {
20 
21             Person xs = new Person("小三");
22             Person xh = new Person("小紅");
23             Person xm = new Person("小明");
24             EatDelegate xsEat = new EatDelegate(xs.Eat);
25             EatDelegate xhEat = new EatDelegate(xh.Eat);
26             EatDelegate xmEat = new EatDelegate(xm.Eat);
27             EatDelegate eatChain = null;
28             Console.WriteLine("小三、小紅和小明一起吃零食");
29             eatChain = xsEat + xhEat + xmEat;
30             eatChain("零食");
31             Console.WriteLine("小三有約,出去剩下小紅和小明吃零食");
32             eatChain -= xsEat;
33             eatChain("零食");
34             Console.WriteLine("小紅也走了,只剩下小明一個人吃零食");
35             eatChain -= xhEat;
36             eatChain("零食");
37             Console.ReadKey();
38         }
39     }

結果和之前的效果一樣:

接下來我繼續進行深入的改進,看到這里相信你也明白,方法是可以作為參數進行傳遞的,那么委托作為代理對象是不是可以作為方法的參數進行傳遞呢??我們試試,對上面代碼繼續進行改造,因為三個人吃零食是不確定的,所以會用到不確定參數數組,以及要吃的食物參數,所以改造如下:

 1     public class Person
 2     {
 3         public string Name { get; set; }
 4 
 5         public Person(string name)
 6         {
 7             this.Name = name;
 8         }
 9 
10         public void Eat(string food)
11         {
12             Console.WriteLine(this.Name + "" + food);
13         }
14     }
15     delegate void EatDelegate(string food);
16     class Program
17     {
18         static void Main(string[] args)
19         {
20 
21             Person xs = new Person("小三");
22             Person xh = new Person("小紅");
23             Person xm = new Person("小明");
24             EatDelegate xsEat = new EatDelegate(xs.Eat);
25             EatDelegate xhEat = new EatDelegate(xh.Eat);
26             EatDelegate xmEat = new EatDelegate(xm.Eat);
27             EatDelegate eatChain = null;
28             Console.WriteLine("小三、小紅和小明一起吃零食");
29             EatSomething("零食", xsEat, xhEat, xmEat);
30             Console.WriteLine("小三有約,出去剩下小紅和小明吃零食");
31             EatSomething("零食", xhEat, xmEat);
32             Console.WriteLine("小紅也走了,只剩下小明一個人吃零食");
33             EatSomething("零食", xmEat);
34             EatSomething(null, null);
35             Console.ReadKey();
36         }
37 
38         static void EatSomething(string food, params EatDelegate[] ed)
39         {
40             EatDelegate eatChain = null;
41             if (ed == null)
42             {
43                 Console.WriteLine("都走了,沒人吃零食");
44             }
45             else
46             {
47                 foreach (var chain in ed)
48                 {
49                     eatChain += chain;
50                 }
51                 eatChain(food);
52                 Console.WriteLine();
53             }
54         }
55     }

結果打印出:

上述我們就實現了將委托作為參數進行傳遞並進行動態的調用 !

至此,零食也吃完了,想必你對委托有了一定的了解了,那么難道你沒有疑問?委托這么強大,它到底是什么東西?委托鏈又是怎樣實現的呢?請看下文

委托實質

我們運用反編譯工具查看上述生成的應用程序的IL代碼,如圖:

由圖中我們得出的信息是:(1)我們聲明的委托 EatDelegate 原來是個類,而且還是可不可繼承的密封類。(2)該委托還繼承多播委托  MulticastDelegate ,並且該多播委托最終繼承於 Delegate ,接下來我們看看這個委托:

我們注意到在這委托里面有個IntPtr,后面接着的變量 _methodPtr 這個就是方法指針,我們說到委托鏈就是存的方法指針,那有很多方法它是怎么將這么多方法指針添加進去的呢?之前看過園友老趙中一篇文章,通過IL代碼可以直接看c#代碼,只是把你編寫的C#代碼進行了再一次編譯而已於是我查看我寫的 EatSomething 方法如圖:

方法中添加委托鏈這一段 eatChain += chain; 被編譯成如圖,於是我點擊查看委托中的 Combine 方法,進去后又看到一個方法如圖:

若是在一個委托中添加另外一個委托,先是調用Delegate中的Combine方法,若檢測第一個委托不為空則調用CombineImpl方法,將新添加的委托b添加進去,此時我查看CombineImpl方法,后面緊着就是出錯拋異常,而且是個虛方法,確定應該是被重寫了,既然聲明的委托繼承於多播委托,多播委托繼承委托(Delegate),肯定是在這兩個委托類中,終於在多播委托  MulticastDelegate 中重寫了這個方法,如圖:

接着查看其方法得到如下

 

通過這幅圖和上幅圖看到,多播委托中 _invovationList object對象在CombineImpl中被轉換成了數組,同時將其num初始化為1,並將多播委托中的要添加的方法指針數量 _invocationCount 賦給num,一直往下,當其添加的數量不夠時,此時將 _invovationList  重新創建數組,數組長度再賦值,有點類似StringBuilder!所以委托鏈的整個過程就是:新添加的委托方法將新創建一個委托對象,並將其方法指針存入最終的父類的變量Intptr中,同時將創建的對象添加到委托數組中去。

Invoke

 聲明委托后查看其IL代碼都有三個方法,最主要的是Invoke方法,如圖:

咦,發現里面怎么有個參數food同時和聲明委托方法簽名一致。於是試試將聲明委托的返回值改為有返回值的,此時編譯生成再來查看果然是一樣的。也就是說當你實例化委托對象所指的方法時,此時同樣可以用實例化委托對象的 Invoke 指向該方法,也就是說調用委托其實就是調用委托中的Invoke方法,並遍歷委托里面的數組,依次調用里面的方法。

總結

(1)聲明委托的本質是聲明一個類,並且該聲明委托extends=>MulticastDelegate=>Delegate

(2)委托鏈的本質就是新添加的方法將創建一個新的委托對象,並將其方法指針存入最終父類Delegate中的變量Intptr中,與此同時將新創建的對象添加到委托的對象數組中去

(3)調用委托的本質是調用實例化委托對象中的Invoke方法,遍歷其委托對象數組,依次調用數組中的方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM