Effective C# 原则21:用委托来表示回调
我:“儿子,到院子里除草去,我要看会书。”
斯科特:“爸,我已经打扫过院子了。”
斯科特:“爸,我已经把草放在除草机上了。”
斯科特:“爸,除草机不能启动了。”
我:“让我来启动它。”
斯科特:“爸,我做好了。”
这个简单的交互展示了回调。我给了我儿子一个任务,并且他可以报告状态来(重复的)打断我。而当我在等待他完成任务的每一个部份时,我不用阻塞我自己的进程。他可以在有重要(或者事件)状态报告时,可以定时的打断我,或者向我询求帮助。回调就是用于异步的提供服务器与客户之间的信息反馈。它们可能在多线程中,或者可能是简单的提供一个同步更新点。在C#里是用委托来表示回调的。
委托提供了一个类型安全的回调定义。尽管委托大多数是为事件使用的,但这不应该是C#语言中唯一使用这一功能的地方。任何时候,如果你想在两个类之间进行通信,而你又期望比使用接口有更少的偶合性,那么委托是你正确的选择。委托可以让你在运行确定(回调)目标并且通知用户。委托就是包含了某些方法的引用。这些方法可以是静态方法,也可以是实例方法。使用委托,你可以在运行时确定与一个或者多个客户对象进行交互。
多播委托包含了添加在这个委托上的所有单个函数调用。有两点要注意的:它不是异常安全的,并且返回值总是委托上最后一个函数调用后返回的值。
在多播委托调用的内部,每一个目标都会成功的调用。委托不会捕获任何的异常,也就是说,在委托链中抛出的任何异常都会终止委托链的继续调用。
在返回值上也存在一个简单的问题。你可以定义委托有返回值或者是void。你可能会写一个回调函数来检测用户的异常中断:
public delegate bool ContinueProcessing();
public void LengthyOperation( ContinueProcessing pred )
{
foreach( ComplicatedClass cl in _container )
{
cl.DoLengthyOperation();
// Check for user abort:
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;
}
}
这时,我已经定义好了程序的语义,因此委托链上的每个委托必须返回真以后,才能继续调用。
委托为运行时回调提供了最好的方法,用户简单的实现用户对类的需求。你可以在运行时确定委托的目标。你可以支持多个用户目标,这样,用户的回调就可以用.Net里的委托实现了。