现在的位置: 首页 > 综合 > 正文

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

2011年06月09日 ⁄ 综合 ⁄ 共 4808字 ⁄ 字号 评论关闭

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

Item 21: Express Callbacks with Delegates

我:“儿子,到院子里除草去,我要看会书。”
斯科特:“爸,我已经打扫过院子了。”
斯科特:“爸,我已经把草放在除草机上了。”
斯科特:“爸,除草机不能启动了。”
我:“让我来启动它。”
斯科特:“爸,我做好了。”

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

Item 21: Express Callbacks with Delegates
Me: "Son, go mow the yard. I'm going to read for a while."

Scott: "Dad, I cleaned up the yard."

Scott: "Dad, I put gas in the mower."

Scott: "Dad, the mower won't start."

Me: "I'll start it."

Scott: "Dad, I'm done."

This little exchange illustrates callbacks. I gave my son a task, and he (repeatedly) interrupted me with the status. I did not block my own progress while I waited for him to finish each part of the task. He was able to interrupt me periodically when he had an important (or even unimportant) status to report or needed my assistance. Callbacks are used to provide feedback from a server to a client asynchronously. They might involve multithreading, or they might simply provide an entry point for synchronous updates. Callbacks are expressed using delegates in the C# language.

Delegates provide type-safe callback definitions. Although the most common use of delegates is events, that should not be theonly time you use this language feature. Any time you need to configure the communication between classes and you desire less coupling than you get from interfaces, a delegate is the right choice. Delegates let you configure the target at runtime and notify multiple clients. A delegate is an object that contains a reference to a method. That method can be either a static method or an instance method. Using the delegate, you can communicate with one or many client objects, configured at runtime.

Multicast delegates wrapall the functions that have been added to the delegate in a single function call. Two caveats apply to this construct: It is not safe in the face of exceptions, and the return value will be the return value of the last function invocation.

Inside a multicast delegate invocation, each target is called in succession. The delegate does not catch any exceptions. Therefore, any exception that the target throws ends the delegate invocation chain.

A similar problem exists with return values. You can define delegates that have return types other than void. You could write a callback to check for user aborts:

public delegate bool ContinueProcessing();

public void LengthyOperation( ContinueProcessing pred )
{
  foreach( ComplicatedClass cl in _container )
  {
    cl.DoLengthyOperation();
    // Check for user abort:
    if (false == pred())
      return;
  }
}

 

It works as a single delegate, but using it as a multicast is problematic:

ContinueProcessing cp = new ContinueProcessing (
  CheckWithUser );
cp += new ContinueProcessing( CheckWithSystem );
c.LengthyOperation( cp );

 

The value returned from invoking the delegate is the return value from the last function in the multicast chain. All other return values are ignored. The return from the CheckWithUser() predicate is ignored.

You address both issues by invoking each delegate target yourself. Each delegate you create contains a list of delegates. To examine the chain yourself and call each one, iterate the invocation list yourself:

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;
  }
}

 

In this case, I've defined the semantics so that each delegate must be true for the iteration to continue.

Delegates provide the best way to utilize callbacks at runtime, with simpler requirements on client classes. You can configure delegate targets at runtime. You can support multiple client targets. Client callbacks should be implemented using delegates in .NET.

 
   

抱歉!评论已关闭.