淺談.NET中的委托


  委托、事件、反射、特性等.NET中的高級特性,對這些特性的掌握和熟練運用,往往成為.NET程序員從入門到中級的評價標准。這篇文章DebugLZQ談一下.NET中的委托特性在.NET框架中是如何實現的,如文章題目說說:淺談.NET中委托的本質。

  委托這一特性對於有過C++編程經驗的程序員來說並不陌生,C++中的函數指針和委托確實非常相似,很多人喜歡吧.NET中的委托稱為“安全的函數指針”。DebugLZQ這里不去爭論這種說法正不正確,但委托確實實現了和函數指針非常相似的功能,那就是程序回調指定方法的機制。

  1、委托的基本原理

  在委托的內部,包含了一個指向某個方法的指針,在這一點上,委托的實現機制和C++的函數指針完全相同。之所以稱委托為安全的,是因為委托和其他.NET成員一樣,是一種類型,任何委托對象都是System.Delegate的某個派生類的一個對象,在.NET框架中,委托的類結構如下圖所示:

  從這個結構中可以看出任何自定義的委托都繼承自父類System.Delegate。在這個類中,定義了大部分委托的特性,而關於System.MulticastDelegate的特性,后面將介紹。

下面分析一個具體的例子,代碼如下:

using System;

namespace 委托本質
{
    class Program
    {
        /// <summary>
        /// DebugLZQ解析委托
        /// 定義委托。
        /// </summary>
        /// <param name="i">接受一個整型參數</i>
        public delegate void TestDelegate(int i);
        static void Main(string[] args)
        {
            //調用委托方法
            TestDelegate d = new TestDelegate(PrintMessage1);
            d(0);
            d(1);            
            Console.Read();
        }

        /// <summary>
        /// 一個靜態方法,符合TestDelegate的定義
        /// </summary>
        /// <param name="i">整型參數</param>
        static void PrintMessage1(int i)
        {
            Console.WriteLine("" + i + "個方法");
        }
    }
}

  在上面代碼中,首先通過public delegate void TestDelegate(int i);定義一個名為TestDeletate的新類型,這個類型繼承自System.MuticastDelegate。而且它會包含一個名為Invoke的方法,該方法接受一個整型的參數並且沒有返回值。這些步驟是由C#編譯器自動完成的。
  然后聲明一個TestDelegate的對象d,並且綁定了一個靜態的方法 void PrintMessage1到該委托上。需要注意的是委托可以接受實例方法,也可以接受靜態方法,其區別將在下面講述。最后,也是令人期待的部分,d被調用執行:d(0)、d(1);這里各位可能會產生困惑,事實上,這只是C#設計者為簡化程序員的輸入而設計的一種語法而已。在本質上,委托的調用就是執行了在定義委托時生成的Invoke方法。

  為了容易理解,我們完全可以把委托的調用部分寫成如下形式:

d.Invoke(0);
d.Invoke(1);

  當委托執行時,.NET 檢查委托對象並找到PrintMessage1(int i)方法,然后把參數傳遞給該方法並且執行。

下面是程序的運行結果:

小結:委托是一類繼承自System.Delegate的類型,每個委托對象至少包含一個指向某個方法的指針,該方法可以是實例方法,也可以是靜態方法。委托實現了回調方法的機制,能夠幫助程序員更加簡潔優美的設計面向對象程序。

  2.委托的內部結構

  為了進一步弄清楚委托的本質,我們來介紹下委托的內部結構。下面我們先看一下System.Delegate的結構,如下圖所示:

  _target是一個指向目標實例的引用。當綁定一個實例方法給委托時,該參數會被設置為該方法所在類型的一個實例對象。而當綁定一個靜態方法給委托時,該參數會被設置為null。(委托回調實例方法和靜態方法的本質區別就在這!)

  _methodPtr是一個指向綁定方法代碼的指針,和C++中的函數指針及其類似。綁定靜態方法或是實例方法在這個成員的設置上並沒有不相同。

  事實上,對於繼承自System.MuticastDelegate的自定義委托來說,還有另外一個成員變量:_prev,該指針指向委托鏈中的下一個委托,這個將在下面進行介紹。

  3.委托鏈[鏈式委托]

  委托鏈是一個由委托組成的鏈表,而不是一個新的東西。從1中的圖可以看到,所有的自定義委托都直接集成自System.MulticastDelegate類型,這個類型即是為委托鏈而設計的。

  為了更徹底的理解鏈式委托的實現機制,有必要來看一下System.MulticastDelegate的內部成員,其重要的三個成員如下圖所示:

  前面已經講過System.Delegate的兩個內部成員,System.MulticastDelegate繼承了這兩個成員,並且添加了一個_prev成員,該成員是一個委托的引用變量,當摸個委托被串聯到當前委托的后面時,該成員會被設置指向那個后續委托實例對象。.NET就靠這一引用來逐一找到當前委托的所有后續委托並依此執行。

  DebugLZQ再次強調,鏈式委托是指一個委托的鏈表,而不是指另外一類特殊的委托,當執行鏈上的一個方法時,后續委托將會被依此執行。如何改變執行順序,后面將介紹。System.MuticastDelegate定義了對鏈式委托的支持。在System.Delegate的基礎上,它增加了一個指向后續委托的指針,這樣就實現了一個簡單的鏈表結構。

  為了更深一層的理解鏈式委托,下面來看一個鏈式委托的例子: 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 鏈式委托
{
    class Program
    {
        /// <summary>
        /// 定義的委托。
        /// </summary>
        public delegate void TestMultiDelegate();
        static void Main(string[] args)
        {
            //申明一個委托變量,並綁定第一個方法
            TestMultiDelegate handler = new TestMultiDelegate(PrintMessage1);
            //綁定第二個方法
            handler += new TestMultiDelegate(PrintMessage2);
            //綁定第三個方法
            handler += new TestMultiDelegate(PrintMessage3);
            //檢查結果
            handler();
            Console.Read();
        }
        static void PrintMessage1()
        {
            Console.WriteLine("第一個方法");
        }
        static void PrintMessage2()
        {
            Console.WriteLine("第二個方法");
        }
        static void PrintMessage3()
        {
            Console.WriteLine("第三個方法");
        }
    }
}

 關於以上代碼,不做過多的解釋。為了便於理解,把上面的代碼的核心部分用一種比較復雜的方式進行重寫:

TestMultiDelegate handler = new TestMultiDelegate(PrintMessage1);           
TestMultiDelegate handler2 = new TestMultiDelegate(PrintMessage2);
TestMultiDelegate handler3 = new TestMultiDelegate(PrintMessage3);
TestMultiDelegate handlerhead = handler + handler2 + handler3;
handlerhead.Invoke();

事實上,這兩種寫法的本質完全一樣,只是第一種寫法更簡潔通用。
程序的輸出如下:

從結果可以看出,當程序調用委托鏈的鏈頭handlerhead時,掛在這個委托之后的所有委托方法都被依此調用了。

我們不妨試下把handlerhead的調用改為如下代碼:

handler();

執行之后,會發現執行結果和替換之前一模一樣。事實上,上面這段代碼沒有為handlerhead分配任何委托實例,而僅僅把所有縣城的委托串聯,並讓handlerhead引用到委托鏈的頭上,所以handlerhead和handler實際上引用了同一個委托實例。

  4.必要說明

   鏈式委托的執行順序是:按照委托鏈上的順醋從當前委托開始依次往后執行,如果有需要可以使用GetInvocationList()方法來獲得委托鏈上所有需要執行的委托,並且按照任何希望的順序去執行(Invoke)他們。

  委托可以是帶有返回值的方法,但多余一個帶返回值的方法被添加到委托鏈中時,程序員需要手動地調用委托鏈上的每個方法,否則委托使用者智能得到委托鏈上最后一個被執行的方法的返回值。

  委托的應用場合通常是任務的執行者把細節工作進行再分配,執行者確切地知道什么工作將要被執行,但卻把執行細節委托給其他組件、方法或者程序集。

  5.結束語

  本文旨在把.NET中的委托給說清楚,可能存在紕漏不妥的地方歡迎批評指正!
  今天是七夕情人節,在這里向現在還奮戰在一線的程序員們致敬,祝你們早日找到心儀的另一半!

  最后請點擊下面的綠色通道--關注DebugLZQ,共同交流進步~ 


免責聲明!

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



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