一、前言
前幾天,馬三在與朋友閑聊技術的時候,朋友忽然拋出一個問題,把馬三難倒了,本着求知的精神,回來以后馬三就查閱了相關資料並做了一些實驗,終於把問題搞明白了,因此寫下本篇博客記錄一下。首先,問題是這樣的:“C#中有多播委托,那么在使用多播委托時,假設方法列表中有多個方法,但委托執行到某個方法時拋出異常,那么整個委托的迭代是否會終止呢?如果終止的話,可以使用什么方法容錯,使整個委托鏈中的方法繼續執行呢?如果把多播委托換成事件,那么又會有怎么樣的效果呢?”。
在開始正文之前,還是先公布一下答案吧:
1.C#多播委托執行到某個方法拋出異常的時候,整個委托的迭代將在拋出異常的地方退出終止,后面的方法就不會再去執行了;
2.可以通過自己設計迭代方法來容錯,起到即使拋出異常,委托鏈也不會中止執行的效果;
3.事件與多播委托的效果一樣;
二、前提知識
按照慣例,我們還是先來熟悉一些前提知識以便於我們對后面概念的理解。
1.委托與事件
委托與事件早已是老生常談了,相信做過C#開發的同學一定沒少了用它們,網上也有很多把委托和事件分析得很透徹的文章,馬三在這里推薦一篇感覺不錯的文章—— 張子陽《C#中的委托和事件》。
2.多播委托與委托鏈
相信大家也或多或少地使用過多播委托,即一個委托可以包含多個方法,當調用該委托的時候,將會依次執行委托鏈中的方法。而委托鏈也是一個委托,只是它是把多個委托鏈在了一起,里面存儲着多個委托的引用。可以說,委托鏈是實現多播委托的途徑,多播委托是委托鏈實現的效果。
圖1:委托鏈示意圖
三、實例與分析
下面我們結合代碼來驗證與分析一下上面的結論,首先還是先上一下代碼:
先定義一個具有具體方法的類 MultiDelegate,一會我們的委托將會調用這里面的方法:
class MultiDelegate { /// <summary> /// 會拋出異常的方法1 /// </summary> public static void Func1() { Console.WriteLine("方法1,會拋出異常!"); throw new Exception("拋出異常!"); } /// <summary> /// 正常方法2 /// </summary> public static void Func2() { Console.WriteLine("方法2"); } }
然后我們直接在Main方法中定義並調用多播委托,然后觀察結果:
//創建多播委托 multiDelegate = MultiDelegate.Func1; multiDelegate += MultiDelegate.Func2; //調用委托,觀察結果 try { multiDelegate(); } catch (Exception e) { Console.WriteLine(e); }
結果如下,可以看到在調用Func1方法以后拋出了異常,整個委托的迭代在此處終止,后面的Func2也不再執行了。
圖2:多播委托遇到異常終止執行
為了避免這種情況的發生,使得我們的程序具有一定的容錯機制。即使在委托拋出異常的時候,后面的方法依舊可以執行,我們需要自定義一個委托方法列表的迭代方法。眾所周知,委托本質上也是一個類,而Delegate類定義了GetInvocationList()方法,它返回Delegate的委托鏈中的對象數組。我們可以通過這個方法拿到委托鏈中的對象,然后建立自己的迭代方法,從而解決多播委托在拋出異常后終止的問題,具體的代碼如下:
//手動迭代委托方法列表,可以處理拋出異常后委托鏈終止執行的問題 //定義方法列表數組,使用GetInvocationList() //注意使用的是Delegate類,不是delegate關鍵字 Delegate[] myDelegates = multiDelegate.GetInvocationList(); foreach (var @delegate in myDelegates) { var delegateItem = (Action) @delegate; //分別調用委托 try { delegateItem(); } catch (Exception e) { Console.WriteLine(e); } }
關於代碼的解釋和一些要注意的地方已經在注釋里面標明了,它的執行效果如下圖所示:
圖3:自定義多播委托的迭代方法
事件可以理解為委托的一個實例化對象,通過+=和-=我們可以注冊或者反注冊一個Handler,它的內部也是使用委托來實現的。事件和多播委托的效果在異常處理上面是一樣的,即遇到異常的時候,后面的方法也會終止執行,我們也可以通過和委托一樣的方法來自定義委托鏈迭代方法來解決這個問題,具體的代碼如下:
//依次注冊事件 multiEvent += MultiDelegate.Func1; multiEvent += MultiDelegate.Func2; //調用事件,觀察結果 try { multiEvent(); } catch (Exception e) { Console.WriteLine(e); } Console.WriteLine("---------------------------分割線------------------------------"); //手動迭代委托方法列表,可以處理拋出異常后委托鏈終止執行的問題 //定義方法列表數組,使用GetInvocationList() //注意使用的是Delegate類,不是delegate關鍵字 Delegate[] myDelegates = multiEvent.GetInvocationList(); foreach (var @delegate in myDelegates) { var delegateItem = @delegate as Action; try { delegateItem(); } catch (Exception e) { Console.WriteLine(e); } }
其實利用上面的方法我們還可以實現其他的很多效果,比如說獲取並處理多播委托的返回值。在我們調用一個多播委托的時候,其返回值一般都是委托鏈中的最后一個方法的返回值,比如有Method1 返回1,Method2返回2,當我們把他們都注冊到一個多播委托上並調用的時候,我們會得到一個結果為2的返回值。下面的代碼演示了如何獲取並處理多播委托的委托鏈中的每個方法的返回值:
/// <summary> /// 帶有返回值的函數
/// </summary> /// <returns></returns>
public static int GetOne() { return 1; } /// <summary> /// 帶有返回值的函數 /// </summary> /// <returns></returns> public static int GetTwo() { return 2; }
getResultDelegate = MultiDelegate.GetOne; getResultDelegate += MultiDelegate.GetTwo; Console.WriteLine("直接調用委托返回的一般是最后一個方法的返回值:" + getResultDelegate()); //手動迭代委托方法列表,可以獲取並處理每個委托的返回值 //定義方法列表數組,使用GetInvocationList() //注意使用的是Delegate類,不是delegate關鍵字 int sum = 0; Delegate[] resultDelegates = getResultDelegate.GetInvocationList(); foreach (var @delegate in resultDelegates) { var delegateItem = @delegate as GetResult; sum += delegateItem(); Console.WriteLine("獲取單個委托的返回值:" + delegateItem()); } Console.WriteLine("多個委托的返回值總和:" + sum);
代碼的執行結果如下,我們一次獲取並打印了委托鏈中每個方法的返回值,並對它們進行了求和:
圖4:處理多播委托的返回值
四、總結
文章的最后我們再來總結一下:C#多播委托執行到某個方法拋出異常的時候,整個委托的迭代將在拋出異常的地方退出終止,后面的方法就不會再去執行了;可以通過自己設計迭代方法來容錯,起到即使拋出異常,委托鏈也不會中止執行的效果;事件與多播委托的效果一樣。
本篇博客中的代碼已經通過到Github:https://github.com/XINCGer/Unity3DTraining/tree/master/SomeTest/MultiDelegateException 歡迎fork!
作者:馬三小伙兒
出處:http://www.cnblogs.com/msxh/p/8625586.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!