【问题标题】:Lambdas for event handlers?用于事件处理程序的 Lambda?
【发布时间】:2008-12-21 18:05:36
【问题描述】:

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

例如,我经常发现我的事件处理程序是(或至少一开始是)简单的单行器,它们设置状态值,或调用另一个函数,或在另一个上设置属性对象等。对于这些,我应该用另一个简单的函数来混乱我的类,还是应该在我的构造函数中将一个 lambda 填充到事件中?

在这种情况下,lambda 有一些明显的缺点:

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

出于上述原因,这两件事并没有让我很困扰。我可以解决这两个问题,如果它们真的是问题,通过将 lambda 存储在成员委托中,但这会破坏使用 lambdas 的方便和保留类的目的干净整洁。

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

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

    再次幸运的是,这并不总是一个问题,因为我不经常在构造函数中创建临时对象。不过,我可以想象一个我做过的场景,并且我无法轻松地将其范围限定在 lambda 之外。

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

所以我想借鉴其他人的建议和经验,也许是那些具有其他语言经验且具有函数式编程特性的人。这种事情有什么既定的最佳实践吗?您是否会避免在事件处理程序中或在 lambda 明显超出其封闭范围的其他情况下使用 lambda?如果不是,您会在多大的阈值下决定使用实函数而不是 lambda?上述任何陷阱有没有严重伤害任何人?有没有我没有想到的陷阱?

【问题讨论】:

    标签: c# lambda event-handling


    【解决方案1】:

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

    1. 解压事件参数。
    2. 使用适当的参数调用命名方法。

    完成后,我避免使用无法完全用于其他目的的事件处理程序方法将我的类命名空间弄乱,并强迫自己考虑我确实实现的操作方法的需求和目的,通常会导致更清洁的代码。

    【讨论】:

    • +1。这类似于我在本机代码中的 WndProc 中所做的。
    • 嘿,是的……这就是我的来历。
    【解决方案2】:

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

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

    换句话说,如果您不在 lambda 中访问包含范围,它不会生成闭包。

    不过,这有点“道听途说”,我希望有更了解 C# 编译器的人参与进来。

    话虽如此,旧的 C# 2.0 匿名委托语法也做了同样的事情,而且我几乎总是将匿名委托用于短事件处理程序。

    您已经很好地介绍了各种优缺点,如果您需要解开事件处理程序,请不要使用匿名方法,否则我完全赞成。

    【讨论】:

    • C# 编译器只有当你在 lambda 中使用它时才允许访问范围内的变量,否则,这不是问题(这适用于匿名方法和 lambdas)。跨度>
    【解决方案3】:

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

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

    所以你认为编译器足够聪明的假设是正确的。

    【讨论】:

    • 这证实了我在下面的怀疑。感谢您查看 IL。
    • 没问题,我认为它也可以这样工作,但想确认一下我自己的好奇心
    【解决方案4】:

    lambda 的大部分相同特性同样适用于您可以使用它们的其他地方。如果事件处理程序不适合他们,我想不出更好的办法。它是一个单点独立的逻辑单元,位于其单点上。

    在许多情况下,该活动旨在获取一些上下文,结果证明正好适合手头的工作。

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

    【讨论】:

      【解决方案5】:

      关于 lambdas,我最近询问的this question 在接受的答案中有一些关于对对象寿命的影响的相关事实。

      我最近了解到的另一件有趣的事情是,C# 编译器在其捕获和保持活动的事物方面将多个闭包理解为与单个闭包相同的作用域。可悲的是,我找不到这个的原始来源。如果我再次偶然发现它,我会添加它。

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

      另一方面,我确实使用了一种稍微不同的事件机制,我发现它比 C# 语言功能更可取:用 C# 重写的 iOS 风格的 NotificationCenter,调度表由 Type(派生自 Notification ) 并带有 Action 值。这最终允许单行“事件”类型定义,如下所示:

      public class UserIsUnhappy : Notification { public int unhappiness; }
      

      【讨论】:

        猜你喜欢
        • 2010-12-17
        • 2011-01-28
        • 1970-01-01
        • 1970-01-01
        • 2019-02-03
        • 2017-10-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多