# 原则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:使用动态接收匿名类型参数