9

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

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

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

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

出于上述原因,这两件事并不困扰我。而且我可以通过将 lambda 存储在成员委托中来解决这两个问题,如果它们确实是问题的话,但这会破坏使用 lambda 来方便和保持类整洁的目的。

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

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

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

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

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

4

5 回答 5

4

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

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

完成此操作后,我避免使用无法干净地用于其他目的的事件处理程序方法使我的类名称空间混乱,并强迫自己考虑我确实实现的操作方法的需求和目的,通常会产生更清晰的代码。

于 2008-12-21T18:19:07.747 回答
2

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

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

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

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

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

您已经很好地介绍了各种利弊,如果您需要解除事件处理程序的挂钩,请不要使用匿名方法,否则我完全赞成。

于 2008-12-21T18:16:39.903 回答
2

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

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

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

于 2008-12-21T18:33:04.973 回答
1

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

在许多情况下,该事件旨在获得一些背景信息,结果证明它恰好适合手头的工作。

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

于 2008-12-21T18:22:20.843 回答
0

关于 lambdas,我最近提出的这个问题在接受的答案中有一些关于对对象寿命的影响的相关事实。

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

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

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

public class UserIsUnhappy : Notification { public int unhappiness; }
于 2014-04-15T11:00:57.147 回答