事件处理程序的Lambdas?

时间:2008-12-21 18:05:36

标签: c# lambda event-handling

C#3中的Lambda语法使得创建单行匿名方法非常方便。它们比C#2给我们的匿名委托语法有了明显的改进。然而,lambdas的便利性带来了诱惑,在我们不一定需要它们提供的函数式编程语义的地方使用它们。

例如,我经常发现我的事件处理程序(或至少开头)简单的单行设置状态值,或调用另一个函数,或在另一个函数上设置属性对于这些,我是否应该用另一个简单的函数使我的类变得杂乱,或者我应该在构造函数中将lambda填充到事件中?

在这种情况下,lambdas存在一些明显的缺点:

  • 我无法直接调用我的事件处理程序;它只能由事件触发。当然,对于这些简单的事件处理程序,几乎没有时间我需要直接调用它们
  • 我不能从事件中取消我的处理程序。另一方面,我很少需要取消挂钩事件处理程序,所以这不是问题,无论如何

由于上述原因,这两件事并没有太多困扰我。通过将lambda存储在成员委托中,我可以解决这两个问题,如果它们确实存在问题,但这会破坏使用lambdas的便利和保持类的目的清理杂乱无章。

另外还有两件事,我认为可能不那么明显,但可能更有问题。

  • 每个lambda函数在其包含范围内形成闭包。这可能意味着由于闭包维护对它们的引用,在构造函数中较早创建的临时对象保持活动的时间比它们需要的时间长得多。现在希望编译器足够聪明,可以从lambda不使用的闭包中排除对象,但我不确定。有人知道吗?

    幸运的是,这并不总是一个问题,因为我不经常在我的构造函数中创建临时对象。我可以想象一下我所做的一个场景,我不能轻易地将它放在lambda之外。

  • 可维护性可能会受到影响。重要时刻。如果我将一些事件处理程序定义为函数,并将一些事件处理程序定义为lambdas,我担心它可能会使跟踪错误或仅仅了解类更难。后来,如果我的事件处理程序最终扩展,我要么必须将它们移动到类级函数,要么处理我的构造函数现在包含大量实现我的类功能的代码的事实

所以我想借鉴其他人的建议和经验,也许是那些具有其他语言经验和函数式编程功能的人。对于这种事情,是否有任何既定的最佳实践?您是否会避免在事件处理程序中使用lambdas,或者在lambda明显超过其封闭范围的其他情况下使用lambdas?如果没有,你会在什么门槛上决定使用真正的函数而不是lambda?上述任何陷阱都会严重咬伤任何人吗?我有没有想过的任何陷阱?

5 个答案:

答案 0 :(得分:4)

我通常有一个例程专门用于连接事件处理程序。其中,我使用匿名委托或lambdas作为实际处理程序,使它们尽可能短。这些处理程序有两个任务:

  1. 解包事件参数。
  2. 使用适当的参数调用命名方法。
  3. 这样做了,我避免使用无法干净地用于其他目的的事件处理程序方法来混淆我的类命名空间,并强迫自己考虑我实现的操作方法的需求和目的,通常导致清洁代码。

答案 1 :(得分:2)

  

每个lambda函数在其包含范围内形成一个闭包。这可能意味着由于闭包维护对它们的引用,在构造函数中较早创建的临时对象保持活动的时间比它们需要的时间长得多。现在希望编译器足够聪明,可以从lambda不使用的闭包中排除对象,但我不确定。有人知道吗?

根据我的阅读,C#编译器要么生成匿名方法,要么生成匿名内部类,具体取决于它是否需要关闭包含范围。

换句话说,如果你没有从lambda中访问包含范围,它就不会产生闭包。

然而,这有点“传闻”,我希望有一个对C#编译器更有知识的人权衡。

所有这一切,旧的C#2.0匿名委托语法做了同样的事情,我几乎总是使用匿名委托来处理短事件处理程序。

你已经很好地涵盖了各种利弊,如果你需要解开你的事件处理程序,不要使用匿名方法,否则我就全力以赴。

答案 2 :(得分:2)

基于对编译器的一点实验,我会说编译器足够聪明,可以创建一个闭包。我所做的是一个简单的构造函数,它有两个不同的lambdas,用于List.Find()中的谓词。

第一个lamdba使用了硬编码值,第二个使用了构造函数中的参数。第一个lambda在类上实现为私有静态方法。第二个lambda被实现为执行结束的类。

因此,您认为编译器足够智能的假设是正确的。

答案 3 :(得分:1)

lambdas的大多数相同特征在其他可以使用它们的地方同样适用。如果事件处理程序不是他们的地方,我想不出更好。它是一个单点独立的逻辑单元,位于其单点。

在许多情况下,该活动旨在获得一些适合当前工作的上下文。

我认为这是重构意义上的“好气味”之一。

答案 4 :(得分:0)

关于lambdas,this question我最近在接受的答案中提到了一些关于物体寿命影响的相关事实。

我最近学到的另一个有趣的事情是,C#编译器在同一范围内包含多个闭包,就其捕获和保持活动的内容而言。可悲的是,我无法找到原始来源。如果我再次偶然发现它,我会补充一点。

就个人而言,我不使用lambdas作为事件处理程序,因为我觉得当逻辑从请求流向结果时,可读性优势确实存在。事件处理程序往往会添加到构造函数或初始化程序中,但在对象的生命周期中此时很少会调用它。那么为什么我的构造函数应该读取它现在实际发生的事情呢?

另一方面,我总是使用一种略微不同的事件机制,我觉得它比C#语言功能更好:用C#重写的iOS风格的NotificationCenter,带有一个由Type键入的调度表(来自Notification) )和行动<通知>值。这最终允许单行“事件”类型定义,如下所示:

public class UserIsUnhappy : Notification { public int unhappiness; }