委託,不知道你是否有也這樣理解(一)

K戰神發表於2015-04-21

目錄:

  • 什麼是委託
  • 委託的作用
  • 委託的本質
  • 委託鏈
  • 委託鏈返回值

一、什麼是委託?

委託:將符合規則的方法進行包裝。裝載方法引用的盒子。將方法作為引數傳遞。

 class Program
    {
        static void Main(string[] args)
        {
            StaticDelegateDome();
        }
        //定義沒有返回值委託Feedback,需要一個數值型別引數
        internal delegate void Feedback(Int32 value);
        //方法群:靜態、靜態、例項
        private static void FeedbackToConsole(Int32 value)
        {
            Console.WriteLine("這是【靜態方法】文字顯示=" + value);
        }
        private static void FeedbackToMsgBox(Int32 value)
        {
            Console.WriteLine("這是【這是靜態方法】訊息框顯示=" + value);
        }
        private void FeedbackToFile(Int32 value)
        {
            Console.WriteLine("這是【例項方法】檔案顯示=" + value);
        }
        //委託回掉方法
        private static void Counter(Int32 from, Int32 to, Feedback fb)
        {
            for (Int32 val = from; val <= to; val++)
            {
                if (fb != null)
                    fb(val);
            }
        }
        //單個靜態方法註冊
        private static void StaticDelegateDome()
        {
            Console.WriteLine("---------------靜態方法註冊委託例子------------------");
            Counter(1, 3, null);//沒有註冊回撥方法
            Counter(1, 3, new Feedback(Program.FeedbackToConsole));//註冊了一個靜態的文字顯示回撥方法
            Counter(1, 3, new Feedback(FeedbackToMsgBox));//如果存在一個類裡面可以將前面的類可以省略
            Console.WriteLine();
            Console.ReadKey();
        }
        //單個例項方法註冊
        private static void InstanceDelegateDome()
        {
            Console.WriteLine("---------------例項方法註冊委託例子------------------");
            Program p = new Program();
            Counter(1, 3, null);//沒有註冊回撥方法
            Counter(1, 3, new Feedback(p.FeedbackToFile));//註冊一個例項方法           
            Console.WriteLine();
            Console.ReadKey();
        }
        //方法組註冊委託形成委託鏈1
        private static void ChainDelegateDome1(Program p)
        {
            Console.WriteLine("---------------------委託鏈例子--------------------");
            Feedback fb1 = new Feedback(FeedbackToConsole);
            Feedback fb2 = new Feedback(FeedbackToMsgBox);
            Feedback fb3 = new Feedback(p.FeedbackToFile);
            Feedback fbChain = null;
            fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
            fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
            fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
            Counter(1, 2, fbChain);
            Console.WriteLine("------------------移除委託鏈中的方法---------------");
            fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
        }
        //方法組註冊委託形成委託鏈2
        private static void ChainDelegateDome2(Program p)
        {
            Console.WriteLine("---------------------委託鏈例子--------------------");
            Feedback fb1 = new Feedback(FeedbackToConsole);
            Feedback fb2 = new Feedback(FeedbackToMsgBox);
            Feedback fb3 = new Feedback(p.FeedbackToFile);
            Feedback fbChain = null;
            fbChain += fb1;
            fbChain += fb2;
            fbChain += fb3;
            Counter(1, 2, fbChain);
            Console.WriteLine("------------------移除委託鏈中的方法---------------");
            fbChain -= new Feedback(FeedbackToMsgBox);
        }
    }
View Code

二、為什麼委託是類?

看上去委託就是:

  宣告,一個delegate,或者約定一下規則:返回值、引數等。  

internal delegate string PutHandler(string options);

三、委託本質

internal delegate string PutHandler(string options);

對於以上程式碼C#編譯器和CLR會為我們做多少工作呢?

編譯器看到上面的程式碼會翻譯成:

 internal class Feedback : System.MulticastDelegate
    {
        public Feedback(Object obj, IntPtr methodAddress);
        public virtual void Invoke(Int32 value);
        public virtual IAsyncResult BoeginInvoke(Int32 value, AsyncCallback callback, Object obj);
        public virtual void EndInvoke(IAsyncResult result);
    }
View Code

需要注意的是,委託就是類。從以上編譯器給我們們生成的程式碼可以看出,所有的委託都派生自:MulticastDelegate,而MulticastDelegate又派生自Delegate。

而這裡面有幾個重要的屬性需要注意:

欄位 型別 說明
_target System。Object

當委託物件包裝靜態方法時,這個值為null。

當包裝實力方法時,這個引用的是回撥方法要操作的物件

 

_metordPtr System.IntPtr 一個內部整數值,CLR用它標記要回撥的方法
_invocationList   System.Objec 通常為null。在構造一個委託鏈時,它可以引用一個委託陣列。

 

 

 

 

 

 

 

 

 

 

首先,這裡我們先說一下這個委託構造器,它包含兩個引數:一個物件引用obj,一個是要回撥方法的整數值methedAddress。

我們會發現我麼呼叫委託建構函式的時候傳遞的是一個值--靜態方法引用或者例項方法引用。

 

疑問:這也能呼叫?能的話,又是怎樣呼叫的呢?

答案:可以呼叫,那這是怎麼做到的呢?----還是編譯器。

 

上面,編譯器根據我們定義的委託為我們翻譯生成了這個委託的所有程式碼。進一步,編譯器會分析我們的原始碼,確定我們應用的是哪個物件和哪個方法。

現在,編譯器確定了:引用的物件Object和一個標識引用方法的特殊值IntPtr。

這裡的,引用物件傳給構造器的第一個引數obj,這個標識引用方法的特殊值IntPtr就會傳給methodAddress。

對於靜態方法這個obj=null。

與此同時,在構造器內部,這兩個實參分別儲存在_target和_metordPtr這兩個私有欄位中,_invocationList初始化為null。

所以,我們使用委託的例項化,其實就是一個編譯器包裝我們引用方法的過程,這個過程是編譯器分析所引用的物件和特殊標記值,傳給委託構造器,並分別存放在私有欄位_target和_metordPtr中。

而MulticastDelegate又派生自Delegate,Delegate又包含兩個公共屬性Target和MetordPtr,這兩個引數的取值自然而然就是上面提到的MulticastDelegate中的私有欄位_target和_metordPtr。

舉例說明: 

Feedback fb1 = new Feedback(FeedbackToConsole);
Feedback fb3 = new Feedback(p.FeedbackToFile);

我們例項化以上委託,一個是靜態方法,最後一個是例項方法。

 

Delegate類中的Target和MetordPtr有公開了可以對兩個欄位的訪問,我們可以利用這兩個公開屬性,訪問使用。

1、判斷委託引用的例項方法所在的物件,是不是和我們指定的type型別的型別一致。

傳入委託引用和待查型別,確定這個委託引用的例項方法是否就在我們提供的這個型別裡面的例項方法。

static Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d, Type type)
 {
            return ((d.Target != null) && d.Target.GetType() == type);
 }

在主函式中這樣呼叫

Boolean b = DelegateRefersToInstanceMethodOfType(new Feedback(FeedbackToConsole),typeof(Program));

這個返回False,因為是靜態方法Target=null。

2、判斷回撥方法是否與我們提供的方法名一致

傳入回撥方法和指定方法名

static Boolean DelegateRefersToInstanceMethodOfName(MulticastDelegate d, String methodName)
{
            return (d.Method.Name == methodName);
}

 

接下來,我們來看看委託是怎樣回撥註冊的回撥函式的。

我們上面這樣定義過:

 //委託回掉方法
private static void Counter(Int32 from, Int32 to, Feedback fb)
{
            for (Int32 val = from; val <= to; val++)
            {
                if (fb != null)
                    fb(val);
            }
}

編譯器檢測到這是一個委託,所以他會呼叫委託的Invoke(Int32 value)方法。所以:fb(val);  等價於  fb.Invoke(val);

 四、委託鏈

 我們之前定義了一個委託鏈方法:

 //方法組註冊委託形成委託鏈1
private static void ChainDelegateDome1(Program p)
{
            Console.WriteLine("---------------------委託鏈例子--------------------");
            Feedback fb1 = new Feedback(FeedbackToConsole);
            Feedback fb2 = new Feedback(FeedbackToMsgBox);
            Feedback fb3 = new Feedback(p.FeedbackToFile);
            Feedback fbChain = null;
            fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
            fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
            fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
            Counter(1, 2, fbChain);
            Console.WriteLine("------------------移除委託鏈中的方法---------------");
            fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));
}

首先,委託鏈是委託物件構成的集合,所以我們定義了fb1,fb2,fb3三個委託物件。

用Combine方法將這三個委託物件放到一個空白委託物件fbChain中,構造委託鏈。所以fbChain就是一個委託物件集合了。

執行:如果只有一個委託物件,fbChain據直接只想fb1的委託物件上。

fbChain = (Feedback)Delegate.Combine(fbChain, fb1);

再執行:現在是又串聯了兩個委託物件,這樣,編譯器就會新建立一個委託物件,其中_invocationList不為null,而是一個陣列,一一對應串聯繫結的委託物件。而fbChain就會指向這個新建立的委託物件。如下圖:

fbChain = (Feedback)Delegate.Combine(fbChain, fb2);

 

fbChain = (Feedback)Delegate.Combine(fbChain, fb3);

當fb.Invoke(val);發現 _invocationList!=null   ,就會迴圈遍歷呼叫 _invocationList  裡面的陣列。

C#編譯器中 Combine()可以用“+=”,Remove()可以用“-=”代替。

 

五、委託鏈的返回值

首先,如果都有返回值,只保留最後一個返回值。

如果中間一個呼叫出現了異常,後面的都就不能順利呼叫。所以,委託鏈不是非常健壯和完善。

這裡要用到:MulticastDelegate類的GetInvocationList()方法:顯示呼叫委託鏈中的每一個委託,針對每個委託,可以自己定製自己需要的演算法。

 public override sealed Delegate[] GetInvocationList();

這樣就可以呼叫GetInvocationList(),並附給一個委託陣列:

Delegate[] deList = fbChain.GetInvocationList();
foreach (var l in deList)
{
       //do sometyhing
}

這樣,我們就可以顯示迴圈遍歷委託鏈,每個方法的返回值進行處理。

 

總之,編譯器替我們做了大量的翻譯工作。

 

相關文章