条款21:使用委托表达回调
我:“孩子,去院子里铲草,我要读会儿书。”
Scott:“爸爸,我已经把院子打扫干净了。”
Scott:“爸爸,我为割草机充上气了。”
Scott:“爸爸,割草机不能启动。”
我:“我来启动它。”
Scott:“爸爸,我搞好了。”
上面一段很短的交流演示了回调(callback)的概念。我给自己的小孩一个任务,他在完成任务期间不断地打断我。在等待他完成任务的每一部分的过程中,我不需要停下我自己的工作。当他有一个重要(或者不重要)的状态需要报告,或者需要我的协助时,他可以周期性地打断我。回调用于为服务器和客户机之间提供异步的反馈。这中间可能会牵扯到多线程,或者需要为同步更新提供一个入口点。在C#中,回调使用委托来表达。
委托为我们提供了类型安全的回调定义。虽然大多数常见的委托应用都和事件有关,但那并不是C#委托应用场合的全部。当类之间有通信的需要,并且我们期望一种比接口更为松耦合的机制时,委托便是最合适的选择。委托允许我们在运行时配置目标,并可通知多个客户对象。委托对象中包含一个方法引用,该方法可以是静态方法,也可以是实例方法。使用委托,我们可以和一个或多个在运行时配置的客户对象进行通信。
可以将所有包含单个函数调用的委托对象组合为多播委托(multicast delegate)。但在这种构造中有两点需要我们注意:第一,如果有委托调用出现异常,那么这种构造将不能保证安全;第二,整个调用的返回值将为最后一个函数调用的返回值。
在一个多播委托调用中,每一个目标会被顺次调用。委托对象本身不会捕捉任何异常。因此,任何目标抛出的异常都会结束委托链的调用。
返回值也有类似的问题。我们定义的委托可以有具体的返回类型(非void)。例如,我们可能会编写一个回调来检查用户是否要异常结束:
public delegate bool ContinueProcessing();
public void LengthyOperation( ContinueProcessing pred )
{
foreach( ComplicatedClass cl in _container )
{
cl.DoLengthyOperation();
// 检查用户是否要异常结束:
if (false == pred())
return;
}
}
作为单个委托,上面的代码工作得很好,但是如果作为多播委托使用,就会出现问题:
ContinueProcessing cp = new ContinueProcessing (
CheckWithUser );
cp += new ContinueProcessing( CheckWithSystem );
c.LengthyOperation( cp );
多播委托返回的值是委托链上最后一个函数调用的返回值。所有其他的返回值都会被忽略。例如,上面代码中CheckWithUser()的返回值就会被忽略。
我们可以自己调用委托链上的每个委托目标,从而避免上述两个问题。所创建的每一个委托都包含一个委托链表。要检查委托链并调用每一个目标,我们需要自己遍历调用链表:
public delegate bool ContinueProcessing();
public void LengthyOperation( ContinueProcessing pred )
{
bool bContinue = true;
foreach( ComplicatedClass cl in _container )
{
cl.DoLengthyOperation();
foreach( ContinueProcessing pr in
pred.GetInvocationList( ))
bContinue &= pr();
if (false == bContinue)
return;
}
}
在上面的代码中,我们要求每一个委托调用都为true时遍历才能继续。
委托为我们提供了一种在运行时进行回调的最好方式,这种方式对客户类只有非常简单的要求。我们可以在运行时配置委托目标。另外,委托也支持多个客户目标。在.NET中,客户回调应该使用委托来实现。






