CLR筆記:15.委託

來源:互聯網
上載者:User
1.Delegate是型別安全的,也就是說,在編譯期可以檢測出錯誤;而與之相似的Reflection是類型不安全的。
   Delegate是方法地址的指標,而且不區分static和instance方法。
   Delegate是定義在Class之外的,這個平級的Class中包括Delegate要使用的方法。

2.Delegate允許參考型別的協變(covariance)和反協變(contra-variance)。即:    delegate object MyCallBack(FileStream fs);

        string SomeOtherMethod(Stream s)
        {

        }

可以將SomeOtherMethod方法綁定到MyCallBack委託上。
"協變"指對於傳回型別,方法可以派生於委託。
"反協變"指對於參數類型,委託可以派生於方法。

3.委託的方法回調一般這樣寫:        void CallBack(string p1, string p2, TestDele d)
        {
            if (d != null)
            {
                d(p1, p2);
            }
        }

這裡我補充一句廢話:使用CallBack方法是可以傳入null值給TestDele參數的;CallBack方法中要判斷d值是否為空白,null就不能執行方法。

4.Delegate本質:一個類
以下委託聲明:

public delegate string TestDele(int intTest);

等價於這個類:

    public class TestDele : System.MulticastDelete
    {
        public TestDele(int intTest, IntPtr method);   

        public virtual string Invoke(int intTest);

        public virtual IAsyncResult BeginResult(int intTest, AsyncCallback callback, Object object);

        public virtual void EndInvoke(IAsyncResult result);
    }

委託的繼承關係:

MulticastDelegete中有3個欄位非常重要:

欄位 類型 描述
_target System.Object 靜態方法時為null;執行個體方法時為執行個體對象。對外表現為屬性Target
_methodPtr System.IntPtr 一個內部整數值,用來標誌回調方法。對外表現為屬性Method,但此時已將整數轉換為MemberInfo對象
_invocationList System.Object 通常是null,在“委託鏈”中形成一個數組

 因為_target和_methodPtr對外表現為屬性Target和Method,所以可以利用這個資訊,
1)檢查一個委託是否引用一個特定類型的執行個體方法:

        bool CheckDeleteTarget(MulticastDelegate d, Type type)
        {
            return ( (d.Target != null) && (d.Target.GetType() == type) );
        }

2)檢查回調方法是否有特定的名稱:

        bool CheckDeleteMethod(MulticastDelegate d, string methodName)
        {
            return (d.Method.Name = methodName)
        }

回到3.回調方法中
d(p1, p2);   等價於 d.Invoke(p1, p2),後者是這句話在委託類中的本質。

5.委託鏈回調多個方法
Delegate類提供兩個靜態方法Combine和Remove,

        public static Delegate Combine(Delegate a, Delegate b);
        public static Delegate Remove(Delegate source, Delegate value);

使用如下:

            FeedBack fbChain = null;

            FeedBack fb1 = new FeedBack(TestClass.Function);
            FeedBack fb2 = new FeedBack(TestClass.Function2);

            fbChain = (FeedBack)Delegate.Combine(fbChain, fb1);
            fbChain = (FeedBack)Delegate.Combine(fbChain, fb2);

只有一個委託時,_invocationList指向null;多於一個委託時形成委託鏈,產生一個委派物件數組,_invocationList會指向這個數組。

對於Remove方法,如果移除後委託鏈中不再有對象,則返回null,也就是說,_invocationList中允許有一個元素的存在(不同於Combine處)

註:對於void型委託,委託鏈會按照隊列順序依次執行方法;對於有傳回值的委託,委託鏈返回最後一個委託的傳回值。
         在C#中,相應的提供+=與-=來代替Combine和Remove。

6.MulticastDelegate的GetInvocationList()執行個體方法
   如果鏈中一個方法拋出異常,就會停止調用後續對象——這個演算法不夠好。
   為了避免這個問題,即使遇到異常,拋出後,繼續調用後續的委託方法,可以使用GetInvocationList():
   比如:delegateList為一個委託鏈,那麼通過delegateList.GetInvocationList()就可以得到一個委託數組arrayDelegates,遍曆這個數組,在其中使用try...catch...捕獲異常,從而所有的委託方法都可以調用到。

7. 委託中的反射——CreateDelegate()方法與DynamicInvoke()方法
使用委託,是因為在編譯時間不知道要使用哪個回調方法,所以說委託是函數指標。
但如果在編譯時間,連要使用哪個委託都不知道,或者不知道必須要傳遞哪些參數,這時候就需要反射的協助了。
Delegate提供兩個方法:
CreateDelegate(),有若干重載:

    //For 靜態方法委託:
         public static Delegate CreateDelegate(Type type, MethodInfo method);
         public static Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure);
    //For 執行個體方法委託:
         public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method);
         public static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure);

這裡type參數為具體delegate類型,method參數為回調方法名稱,如果有問題就根據throwOnBindFailure判斷是否拋出異常,不拋出異常時就返回null;對於執行個體方法委託,還要額外傳入firstArgument這個執行個體方法的對象。
可以通過反射擷取type參數和method參數,如下:
Type delType = Type.GetType("delegate類型");
以靜態方法為例:MethodInfo mi = typeof(靜態方法所在類).GetMethod("靜態方法名", BindingFlags.NonPublic | BindingFlags.Static)

DynamicInvoke(params Object[] args),執行個體方法,傳遞一組在運行時能確定的參數args。
這樣,虛擬碼大致如下:

Delegate d;
d.CreateDelegate(delType, mi);
d.DynamicInvoke(params);

8.匿名方法——優雅的委託文法
書上說,使用匿名方法有一個尺度:如果回調方法中多於3行代碼,就不要使用。
我認為,不管幾行,我都不會去用——能看懂別人寫的匿名方法就可以。也許這就是BS程式員與CS程式員的區別吧。
書中總結了4條,逐條分析:

第1條,直接使用回呼函數作為參數,不需要構造委派物件
     在下面的樣本中,我們看到,SomeAsyncTask()方法作為參數傳遞,而ThreadPool.QueueUserWorkItem()需要的是一個WaitCallback委派物件參數,.NET可以自動推斷,將方法解析為委託。以下方法1和方法2其實是一樣的。

    internal sealed class AClass
    {
        public static void CallbackWithoutNewingADelegateObject()
        {
            //方法1
            WaitCallback waitCallback = new WaitCallback(SomeAsyncTask);
            ThreadPool.QueueUserWorkItem(waitCallback, 5);

            //方法2
            ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);
        }

        private static void SomeAsyncTask(Object o)
        {
            Console.WriteLine(o);
        }
    }

第2條,不需要定義回調方法——"匿名方法"由此而來

        public static void CallbackWithoutNewingADelegateObject()
        {
            //方法3,於是可以省略SomeAsyncTask方法
            ThreadPool.QueueUserWorkItem(
                delegate(Object obj) { Console.WriteLine(obj); },
                5);
        }

匿名方法中可以有任意行代碼,以上只是一行Console輸出語句

第3條,不需要指定回調方法的參數——不使用方法的參數時,可以省略參數部分
這一條是由第2條演變而來,如下樣本:

            button1.Click += delegate(Object sender, EventArgs e)
            {
                MessageBox.Show("Hello!");
            };

            //簡寫為
            button1.Click += delegate
            {
                MessageBox.Show("Hello!");
            };

因為sender和e兩個參數並不使用,所以省略之,但是CLR仍然會在編譯時間自動產生這兩個參數。如果匿名方法中使用了其中任一個參數,還是要全部聲明。

第4條,在回調方法中可以使用外部的變數
還是基於第2條,因為有了匿名方法,從而可以在其中使用外部的一些變數,這樣就不必多建很多參數傳來傳去的了。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.