# 原則24:使用委托來表達回調
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
我:“兒子,到院子去除草。要去看會書。”
Scott:“爸爸,我清理好院子了。”
Scott:“爸爸,我已經把草放在除草機上了。”
Scott:“爸爸,除草機不能啟動。”
我:“讓我來啟動它。”
Scott:“爸爸,我已經做完了。”
這個簡單的交互展示了回調。我給兒子一個任務,它(重復)報告他的狀態以打斷我。而當我在等待他完成任務的每一個部份時,我不用阻塞我自己的進程。他可以在有重要(或者事件)狀態報告時或者向我詢求幫助時,可以定時的打斷我,。回調就是用于異步的提供服務器與客戶之間的信息反饋。它們可能在多線程中,或者可能是簡單的提供一個同步更新點。在 C# 語言里是用委托來表達回調的。
委托提供類型安全的回調定義。雖然大多數是作為事件使用,那不是 C# 語言唯一使用這個特性的地方。任何時候,如果你想在兩個類之間進行通信,而你又期望比使用接口有更少的偶合性,那么委托是你正確的選擇。委托可以讓你在運行時確定目標并且通知用戶。委托就是包含了某些方法的引用的對象。這些方法可以是靜態方法,也可以是實例方法。使用委托,你可以在運行時確定與一個或者多個客戶對象進行交互。
回調和委托是 C# 語言常見的習慣,組合 lambda 表達式語法的以表達委托。此外,.NET 框架定義了許多常見的委托形式,如 Predicate<T> , Action<> 和 Func<> 。Predicate<T> 是測試條件的布爾函數。 Func<> 傳入多個參數并產生一個單一的結果。是的,這意味著 Func<T,bool> 和 Predicate<T> 是相同的形式。盡管編譯器不會將 Predicate<T> 和 Func<T,bool> 看做相同的。最后, Action<> 有多個參數并且有 void 的返回值類型。
LINQ 就是由這些概念構建出來的。 List<T> 類也包含很多使用回調的方法。看下面的代碼:
```
List<int> numbers = Enumerable.Range(1, 200).ToList();
var oddNumbers = numbers.Find(n => n % 2 == 1);
var test = numbers.TrueForAll(n => n < 50);
numbers.RemoveAll(n => n % 2 == 0);
numbers.ForEach(item => Console.WriteLine(item));
```
Find() 方法傳入一個委托,形式為 Predicate<int> 以檢查隊列中的每個元素。它是很簡單的回調。 Find() 方法使用回調對每個元素進行檢查,并且返回通過謂詞測試的元素。編譯器會將 lambda 表達式,轉換為委托,并使用委托表達回調。
TrueForAll() 同樣它檢查每個元素,而且返回謂詞為 true 的項。 RemoveAll() 移除那些謂詞為 true 的項以修改隊列。
最后, List.ForEach() 方法對隊列的元素執行指定的動作。和前面一樣,編譯器轉換 lambda 表示為方法并創建委托引用這個方法。
你會在 .NET 框架中發現很多這樣的例子。所有 LINQ 都是構建與委托之上的。回調函數是用來處理在 WPF 和 Window Form 上的多線程編程。.NET 框架需要很簡單方法的地方,它會使用委托,即調用者可以用 lambda 表達式來表達。當你需要在 API 中需要回調約定可以遵循這個例子的做法。
由于歷史原因,所有委托都是多播委托。多播委托會一次調用所有添加的目標函數。有兩點需要注意的:如果有異常是不安全的,而且最后執行的目標函數的返回值會作為回調的返回值。
在多播委托調用的內部,每個目標對象會被連續調用。委托不捕捉任何異常。因此,任何異常的拋出將終止委托鏈的調用。
返回值存在一個相似的問題。你可以定義委托有返回類型不是 void 。你可以寫一個回調來檢查用戶中止:
```
public void LengthyOperation(Func<bool> pred)
{
foreach (ComplicatedClass cl in container)
{
cl.DoLengthyOperation();
// Check for user abort:
if (false == pred())
return;
}
}
```
單一委托是可以正常工作的,但是對于多播是有問題的:
```
Func<bool> cp = () => CheckWithUser();
cp += () => CheckWithSystem();
c.LengthyOperation(cp);
```
委托調用的返回值是多播鏈最后一個函數調用的返回值。所有其他返回值都會被忽略。CHeckWithUser() 位置的返回值是會被忽略的。
你可以通過自己調用目標委托解決這個問題。每個創建的委托包含一個委托隊列。自己檢查這個隊列并遍歷調用:
```
public void LengthyOperation2(Func<bool> pred)
{
bool bContinue = true;
foreach (ComplicatedClass cl in container)
{
cl.DoLengthyOperation();
foreach (Func<bool> pr in pred.GetInvocationList())
bContinue &= pr();
if (!bContinue)
return;
}
}
```
在這個例子,我已經定義了每個委托返回值必須是 true 才會繼續執行的語義。
委托提供在運行時利用回調的最好方式,滿足簡單的客戶類上的需求。你可以在運行時確定委托目標。你可以支持多個回調目標。客戶端回調需要使用 .NET 的委托實現。
小結:
使用委托來表達回調。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2083564](/blog/2083564)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數