委託、Lambda表示式、事件系列04,委託鏈是怎樣形成的, 多播委託, 呼叫委託鏈方法,委託鏈異常處理

Darren Ji發表於2014-10-01

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

 

呼叫返回型別為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);
        }
    }
}

3


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

→當執行MySayDel del = SayHello;
4
→當執行del = (MySayDel)Delegate.Combine(del, new MySayDel(SayNice)),在託管堆上又建立MySayDel委託例項指向SayNice方法,接著複製原先的、指向SayHello方法的委託例項,2個委託例項形成委託鏈,即藍色區域部分,棧上的委託變數del指向委託鏈。
5

 

  呼叫返回型別不是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;
        }
    }

6
以上,當呼叫委託不會依次執行委託鏈方法,而是會執行最後註冊的方法。

 

如果我們想得到所有委託方法的返回結果,該如何做到呢?
--委託為我們提供了一個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;
        }
    }

7
以上,通過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>,它有多個過載方法:
8


最後一個形參是返回型別,其餘形參是輸入引數。

    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>,它有多個過載方法:
9

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

 

    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);
        }
    }

10

 

  處理委託鏈異常

在委託鏈中,如果任何一個委託方法丟擲異常,如何處理呢?
--需要遍歷委託鏈,讓每個委託單獨執行,並編寫處理異常程式碼

    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();
        }
    }

11


總結:
○ 如果委託的返回型別是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實現觀察者模式,體驗委託和事件的區別

委託、Lambda表示式、事件系列07,使用EventHandler委託

相關文章