程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 委托、Lambda表達式、事件系列04,委托鏈是怎樣形成的, 多播委托, 調用委托鏈方法,委托鏈異常處理,lambda表達式

委托、Lambda表達式、事件系列04,委托鏈是怎樣形成的, 多播委托, 調用委托鏈方法,委托鏈異常處理,lambda表達式

編輯:C#入門知識

委托、Lambda表達式、事件系列04,委托鏈是怎樣形成的, 多播委托, 調用委托鏈方法,委托鏈異常處理,lambda表達式


委托是多播委托,我們可以通過"+="把多個方法賦給委托變量,這樣就形成了一個委托鏈。本篇的話題包括:委托鏈是怎樣形成的,如何調用委托鏈方法,以及委托鏈異常處理。

 

□ 調用返回類型為void的委托所形成的委托鏈方法
□ 調用返回類型不是void的委托所形成的委托鏈方法
□ 調用返回類型不是void的泛型委托所形成的委托鏈方法
□ 調用Func<T>泛型委托所形成的委托鏈方法
□ 調用Action<T>泛型委托所形成的委托鏈方法
□ 處理委托鏈異常

 

  調用返回類型為void的委托所形成的委托鏈方法

來看下面的例子:

namespace ConsoleApplication3
{
    internal delegate void MySayDel(string msg);
    class Program
    {
        static void Main(string[] args)
        {
            MySayDel del = SayHello;
            del = (MySayDel)Delegate.Combine(del, new MySayDel(SayNice)); //等同於:del += SayNice;           
            del += SayOk;
            del("darren");
        }
        static void SayHello(string msg)
        {
            Console.WriteLine("hello " + msg);
        }
        static void SayNice(string msg)
        {
            Console.WriteLine("nice " + msg);
        }
        static void SayOk(string msg)
        {
            Console.WriteLine("ok " + msg);
        }
    }
}


最後,調用委托執行方法,最先注冊的方法最先執行。"+="是一種"語法糖",內部其實調用了Delegate的靜態方法Combine,形成委托鏈,再把委托鏈賦給委托變量。大致如下:

→當執行MySayDel del = SayHello;

→當執行del = (MySayDel)Delegate.Combine(del, new MySayDel(SayNice)),在托管堆上又創建MySayDel委托實例指向SayNice方法,接著復制原先的、指向SayHello方法的委托實例,2個委托實例形成委托鏈,即藍色區域部分,棧上的委托變量del指向委托鏈。

 

  調用返回類型不是void的委托所形成的委托鏈方法

以上,委托的返回類型是void,當調用委托的時候,依次執行委托鏈的方法。可是,如果委托的返回類型不是void,會不會依次執行委托鏈的方法呢?

    internal delegate int MyCalulateDel(int val1, int val2);
    class Program
    {
        static void Main(string[] args)
        {
            MyCalulateDel del = Add;
            del += Sub;
            Console.WriteLine(del.Invoke(20, 10));
        }
        static int Add(int val1, int val2)
        {
            return val1 + val2;
        }
        static int Sub(int val1, int val2)
        {
            return val1 - val2;
        }
    }


以上,當調用委托不會依次執行委托鏈方法,而是會執行最後注冊的方法。

 

如果我們想得到所有委托方法的返回結果,該如何做到呢?
--委托為我們提供了一個GetInvocationList的實例方法,可以獲取所有委托。

    internal delegate int MyCalulateDel(int val1, int val2);
    class Program
    {
        static void Main(string[] args)
        {
            MyCalulateDel del = Add;
            del += Sub;
            var result = GetResultForEachDel(del, 20, 10);
            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }
        static List<int> GetResultForEachDel(MyCalulateDel del, int val1, int val2)
        {
            List<int> result = new List<int>();
            foreach (MyCalulateDel item in del.GetInvocationList())
            {
                result.Add(item.Invoke(val1, val2));
            }
            return result;
        }
        static int Add(int val1, int val2)
        {
            return val1 + val2;
        }
        static int Sub(int val1, int val2)
        {
            return val1 - val2;
        }
    }


以上,通過GetInvocationList實例方法獲取所有的委托,然後分別調用所有的委托方法。

 

  調用返回類型不是void的泛型委托所形成的委托鏈方法

以上委托只針對int類型,如果不想把類型"寫死",就應該使用泛型委托。

namespace ConsoleApplication5
{
    internal delegate T MyGenericDel<T>();
    class Program
    {
        static void Main(string[] args)
        {
            MyGenericDel<int> d = ReturnOne;
            d += ReturnTwo;
            var result = GetReturnValues(d);
            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }
        //執行所有的泛型委托
        static IEnumerable<TModel> GetReturnValues<TModel>(MyGenericDel<TModel> d) 
        {
            //遍歷委托鏈
            foreach (MyGenericDel<TModel> del in d.GetInvocationList())
            {
                yield return del.Invoke();
            }
        }
        static int ReturnOne()
        {
            return 1;
        }
        static int ReturnTwo()
        {
            return 2;
        }
    }
}

泛型委托,一般是在返回類型名稱後面、方法名稱後面,形參類型名稱後面加上占位符<T>。

 

  調用Func<T>泛型委托所形成的委托鏈方法

而實際上,對於泛型委托,.NET為我們准備了Func<T>,它有多個重載方法:


最後一個形參是返回類型,其余形參是輸入參數。

    class Program
    {
        static void Main(string[] args)
        {
            Func<int> d = ReturnOne;
            d += ReturnTwo;
            var result = GetReturnValues(d);
            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }
        //執行所有的泛型委托
        static IEnumerable<TModel> GetReturnValues<TModel>(Func<TModel> d) 
        {
            //遍歷委托鏈
            foreach (Func<TModel> del in d.GetInvocationList())
            {
                yield return del();
            }
        }
        static int ReturnOne()
        {
            return 1;
        }
        static int ReturnTwo()
        {
            return 2;
        }
    }


 

  調用Action<T>泛型委托所形成的委托鏈方法

如果一個泛型委托沒有返回值,就可以使用Action<T>,它有多個重載方法:

所有的形參都是輸入參數,沒有返回值。

 

    class Program
    {
        static void Main(string[] args)
        {
            Action<string> action = SayOnce;
            action += SayTwice;
            action.Invoke("darren");
        }
        static void SayOnce(string str)
        {
            Console.WriteLine("我只說一次" + str);
        }
        static void SayTwice(string str)
        {
            Console.WriteLine("我第一次說" + str);
            Console.WriteLine("我第二次說" + str);
        }
    }

 

  處理委托鏈異常

在委托鏈中,如果任何一個委托方法拋出異常,如何處理呢?
--需要遍歷委托鏈,讓每個委托單獨執行,並編寫處理異常代碼

    class Program
    {
        static void Main(string[] args)
        {
            Action<string> action = SayOnce;
            action += SayTwice;
            foreach (Action<string> a in action.GetInvocationList())
            {
                try
                {
                    a("darren");
                }
                catch (Exception)
                {
                    Console.WriteLine("有異常");
                }
            }
        }
        static void SayOnce(string str)
        {
            Console.WriteLine("我只說一次" + str);
        }
        static void SayTwice(string str)
        {
            Console.WriteLine("我第一次說" + str);
            Console.WriteLine("我第二次說" + str);
            throw new Exception();
        }
    }


總結:
○ 如果委托的返回類型是void,並且形成委托鏈,只要調用委托就會依次執行委托鏈方法。
○ 如果委托的返回類型不是void,並且形成委托鏈,可以使用委托的GetInvocationList實例方法獲取所有委托,然後遍歷這些委托依次執行委托方法得到返回類型。
○ 泛型委托優先考慮使用Func<T>和Action<T>,如果有返回類型使用Func<T>,如果返回類型為void使用Action<T>
○ 委托鏈的異常處理思路是:遍歷委托鏈中的每個委托,針對每個委托編寫捕獲異常的代碼

 

 

“委托、Lambda表達式、事件系列”包括:

委托、Lambda表達式、事件系列01,委托是什麼,委托的基本用法,委托的Method和Target屬性

委托、Lambda表達式、事件系列02,什麼時候該用委托

委托、Lambda表達式、事件系列03,從委托到Lamda表達式

委托、Lambda表達式、事件系列04,委托鏈是怎樣形成的, 多播委托, 調用委托鏈方法,委托鏈異常處理

委托、Lambda表達式、事件系列05,Action委托與閉包

委托、Lambda表達式、事件系列06,使用Action實現觀察者模式,體驗委托和事件的區別


C# 中怎利用lambda實現委托事件的掛接

委托定義如下:復制代碼 代碼如下:public class SocketSp{public delegate void ReceiveCompleted(byte[] receiveBuffer, int receiveTotalLen,Exception ex);public ReceiveCompleted receiveCompleted;}掛接方定義如下復制代碼 代碼如下:public class LinkOuter{SocketSp linkOuterSocket = new SocketSp();private void test(Socket requestHandleSocket){//此處要掛接 linkOuterSocket.receiveCompleted 事件,同時想將參數requestHandleSocket傳入,以便後續處理。}}第一個想法是利用delegate,但是失敗了。因為雖然掛接上去了,委托傳進的參數丟了,無法進行後續操作。復制代碼 代碼如下:private void test(Socket requestHandleSocket){linkOuterSocket.receiveCompleted += delegate {//To do};}第二個想法是利用Action,結果也失敗了。IDE提示委托Action未采用3個參數。復制代碼 代碼如下:private void test(Socket requestHandleSocket){linkOuterSocket.receiveCompleted += (Action)((outerReceiveBuffer, totalLen, ex) => {//To do});}第三個想法是利用lambda表達式,先與委托掛接,同時利用局部變量的調用,來實現參數傳遞到sendResponse函數中,以便後續操作。復制代碼 代碼如下:private void test(Socket requestHandleSocket){linkOuterSocket.receiveCompleted += new SocketSp.ReceiveCompleted((outerReceiveBuffer,totalLen,ex) =>{byte[] realOuterReceiveBuffer = new byte[totalLen];Array.Copy(outerReceiveBuffer, 0, realOuterReceiveBuffer, 0, totalLen);sendResponse(requestHandleSocket, realOuterReceiveBuffer,"200 OK", "text/html");});}最終用lambda表達式實現了。您可能感興趣的文章:C#用Lambda和委托實現模板方法
 

c#委托鏈的問題

你的委托d1上的確已經掛上了兩個方法,當d1被調用的時候,那兩個方法也的確都被觸發調用到了。
不過多個方法有多個返回值,而只有一個變量來接受,所以後面被調用的方法的返回值,就把前面方法的返回值覆蓋掉了。換句話說,前面的返回值都被丟棄掉了。

這也是定義事件委托的時采用void作為返回值的原因。

【回答補充】

你可以去看看系統類庫提供的委托/事件,都是void返回類型的。

如果你非要做一個帶返回值的委托,那可以這樣做了:
......
public string CreateDelegate()
{
Class1 c1 = new Class1();
MyDelegate d1 = new MyDelegate(c1.delegateMethod1);
d1 += c1.delegateMethod2;
string d = "";
foreach (MyDelegate x in d1.GetInvocationList())
{
d += x("Hello World");
}
return d;
}
......
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved