企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 原则24:使用委托来表达回调 **By D.S.Qiu** **尊重他人的劳动,支持原创,转载请注明出处:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)** 我:“儿子,到院子去除草。要去看会书。” Scott:“爸爸,我清理好院子了。” Scott:“爸爸,我已经把草放在除草机上了。” Scott:“爸爸,除草机不能启动。” 我:“让我来启动它。” Scott:“爸爸,我已经做完了。” 这个简单的交互展示了回调。我给儿子一个任务,它(重复)报告他的状态以打断我。而当我在等待他完成任务的每一个部份时,我不用阻塞我自己的进程。他可以在有重要(或者事件)状态报告时或者向我询求帮助时,可以定时的打断我,。回调就是用于异步的提供服务器与客户之间的信息反馈。它们可能在多线程中,或者可能是简单的提供一个同步更新点。在 C# 语言里是用委托来表达回调的。 委托提供类型安全的回调定义。虽然大多数是作为事件使用,那不是 C# 语言唯一使用这个特性的地方。任何时候,如果你想在两个类之间进行通信,而你又期望比使用接口有更少的偶合性,那么委托是你正确的选择。委托可以让你在运行时确定目标并且通知用户。委托就是包含了某些方法的引用的对象。这些方法可以是静态方法,也可以是实例方法。使用委托,你可以在运行时确定与一个或者多个客户对象进行交互。 回调和委托是 C# 语言常见的习惯,组合 lambda 表达式语法的以表达委托。此外,.NET 框架定义了许多常见的委托形式,如 Predicate&lt;T&gt; , Action&lt;&gt; 和 Func&lt;&gt; 。Predicate&lt;T&gt; 是测试条件的布尔函数。 Func&lt;&gt; 传入多个参数并产生一个单一的结果。是的,这意味着 Func&lt;T,bool&gt; 和 Predicate&lt;T&gt; 是相同的形式。尽管编译器不会将 Predicate&lt;T&gt; 和 Func&lt;T,bool&gt; 看做相同的。最后, Action&lt;&gt; 有多个参数并且有 void 的返回值类型。 LINQ 就是由这些概念构建出来的。 List&lt;T&gt; 类也包含很多使用回调的方法。看下面的代码: ``` List<int> numbers = Enumerable.Range(1, 200).ToList(); var oddNumbers = numbers.Find(n => n % 2 == 1); var test = numbers.TrueForAll(n => n < 50); numbers.RemoveAll(n => n % 2 == 0); numbers.ForEach(item => Console.WriteLine(item)); ``` Find() 方法传入一个委托,形式为 Predicate&lt;int&gt; 以检查队列中的每个元素。它是很简单的回调。 Find() 方法使用回调对每个元素进行检查,并且返回通过谓词测试的元素。编译器会将 lambda 表达式,转换为委托,并使用委托表达回调。 TrueForAll() 同样它检查每个元素,而且返回谓词为 true 的项。 RemoveAll() 移除那些谓词为 true 的项以修改队列。 最后, List.ForEach() 方法对队列的元素执行指定的动作。和前面一样,编译器转换 lambda 表示为方法并创建委托引用这个方法。 你会在 .NET 框架中发现很多这样的例子。所有 LINQ 都是构建与委托之上的。回调函数是用来处理在 WPF 和 Window Form 上的多线程编程。.NET 框架需要很简单方法的地方,它会使用委托,即调用者可以用 lambda 表达式来表达。当你需要在 API 中需要回调约定可以遵循这个例子的做法。 由于历史原因,所有委托都是多播委托。多播委托会一次调用所有添加的目标函数。有两点需要注意的:如果有异常是不安全的,而且最后执行的目标函数的返回值会作为回调的返回值。 在多播委托调用的内部,每个目标对象会被连续调用。委托不捕捉任何异常。因此,任何异常的抛出将终止委托链的调用。 返回值存在一个相似的问题。你可以定义委托有返回类型不是 void 。你可以写一个回调来检查用户中止: ``` public void LengthyOperation(Func<bool> pred) { foreach (ComplicatedClass cl in container) { cl.DoLengthyOperation(); // Check for user abort: if (false == pred()) return; } } ``` 单一委托是可以正常工作的,但是对于多播是有问题的: ``` Func<bool> cp = () => CheckWithUser(); cp += () => CheckWithSystem(); c.LengthyOperation(cp); ``` 委托调用的返回值是多播链最后一个函数调用的返回值。所有其他返回值都会被忽略。CHeckWithUser() 位置的返回值是会被忽略的。 你可以通过自己调用目标委托解决这个问题。每个创建的委托包含一个委托队列。自己检查这个队列并遍历调用: ``` public void LengthyOperation2(Func<bool> pred) { bool bContinue = true; foreach (ComplicatedClass cl in container) { cl.DoLengthyOperation(); foreach (Func<bool> pr in pred.GetInvocationList()) bContinue &= pr(); if (!bContinue) return; } } ``` 在这个例子,我已经定义了每个委托返回值必须是 true 才会继续执行的语义。 委托提供在运行时利用回调的最好方式,满足简单的客户类上的需求。你可以在运行时确定委托目标。你可以支持多个回调目标。客户端回调需要使用 .NET 的委托实现。 小结: 使用委托来表达回调。 欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德! 有关本书的其他章节翻译请[点击查看](/category/297763),转载请注明出处,尊重原创! 如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。 转载请在**文首**注明出处:[http://dsqiu.iteye.com/blog/2083564](/blog/2083564) 更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)