Effective C# 原则21:用委托来表示回调

来源:互联网 发布:学生化妆品知乎 编辑:程序博客网 时间:2024/05/20 23:58

我:“儿子,到院子里除草去,我要看会书。”

  斯科特:“爸,我已经打扫过院子了。”

  斯科特:“爸,我已经把草放在除草机上了。”

  斯科特:“爸,除草机不能启动了。”

  我:“让我来启动它。”

  斯科特:“爸,我做好了。”

  这个简单的交互展示了回调。我给了我儿子一个任务,并且他可以报告状态来(重复的)打断我。而当我在等待他完成任务的每一个部份时,我不用阻塞我自己的进程。他可以在有重要(或者事件)状态报告时,可以定时的打断我,或者向我询求帮助。回调就是用于异步的提供服务器与客户之间的信息反馈。它们可能在多线程中,或者可能是简单的提供一个同步更新点。在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里的委托实现了。