爸爸:“儿子,去把院子里的草割一下。我看会儿报。”
儿子:“爸爸,我把院子打扫干净了。”
儿子:“爸爸,我给割草机加了油。”
儿子:“爸爸,割草机发动不了。”
爸爸:“我来发动它。”
儿子:“爸爸,我把草割好了。”
这个就是回调处理的例子。爸爸给儿子指派一项任务,儿子报告任务进展。在爸爸等待儿子完成工作的时候,并没有阻塞他自己正在进行的活动。如果儿子有什么重要(即便是不重要的)事情,可以打断爸爸的活动来报告情况。回调被用在服务器端和客户端的异步通信。这或许会包含多线程操作,或者仅仅是简单的提供同步更新的入口点。回调处理在C#语言中是通过委托来实现的。
委托为回调提供了一个安全的定义。尽管我们一般将委托用于处理事件,但这并不是它唯一发挥作用的地方。当我们需要配置类之间的通信,又希望实现较接口而言的松耦合时,我们就可以使用委托。委托可以让我们可以在运行时期对目标进行配置并通知多个客户端。委托是一个包含方法引用的对象。通过使用委托,我们可以于一个或多个客户端对象通信,在运行时期改变它们的配置。
多路广播委托可以在一个函数被调用时触发多个添加到委托中的函数。对于这种方式我们应当注意两点:首先它对异常不安全,其次它的返回值将是最后一次相应函数的返回值。
在多路广播委托的响应中每个目标都必须要成功的被调用。委托不会捕获任何相关的异常。因此一旦有目标抛出异常,则整个委托响应链就会被中断。
同样类似的问题还会出现在返回值上。我们可以为委托定义返回值,在回调的时候检查这些返回:
public void LengthyOperation(ContinueProcessing pred)
{
foreach (ComplicatedClass cl in _container)
{
c1.DoLengthOperation();
if (false == pred())
{
return;
}
}
}
对于单一的委托来说这没有什么问题,但是当我们在多路广播委托时使用时:
cp += new ContinueProcessing(CheckWithUser);
c.LengthyOperation(cp);
委托的返回值将会是多路广播委托链中最后一个函数的返回值。所有的其它返回值都被忽略了,就如同本例中的CheckWithUser()。
我们可以通过手工来调用每个委托。我们创建的每个委托包含一个委托的列表。我们需要遍历这个列表来检查委托链中的返回值。
public void LengthyOperation(ContinueProcessing pred)
{
bool bContinue = true;
foreach (ComplicatedClass cl in _container)
{
c1.DoLengthOperation();
foreach(ContinueProcessing pr in pred.GetInvocationList())
{
bCountinue &= pr();
}
if (false == bCountinue)
{
return;
}
}
}
这样我们就从语法上定义了每个委托必须为true
委托提供了在运行时使用回调的最好方法,对客户端也没有特殊的要求。我们可以在运行时处理委托目标,同时也可以支持多路广播委托。在.Net中客户端回调应当使用委托来实现。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录
对于委托,我又想起来那个经典的.Net睡前故事...