事件处理程序的 Lambda 表达式?
C# 3 中的 Lambda 语法使创建单行匿名方法变得非常方便。 它们相对于 C# 2 提供的更冗长的匿名委托语法来说是一个明显的改进。 然而,lambda 的便利性带来了在我们不一定需要它们提供的函数式编程语义的地方使用它们的诱惑。
例如,我经常发现我的事件处理程序是(或至少开始)简单的单行代码,用于设置状态值,或调用另一个函数,或在另一个对象上设置属性等对于这些,我应该用另一个简单的函数来扰乱我的类,还是应该将 lambda 填充到我的构造函数中的事件中?
在这种情况下,lambda 有一些明显的缺点:
- 我无法直接调用我的事件处理程序; 它只能由事件触发。 当然,对于这些简单的事件处理程序,我几乎不需要直接调用它们。
- 我无法从事件中取消我的处理程序。 另一方面,我很少需要取消事件处理程序,所以无论如何这不是什么大问题。
出于上述原因,这两件事并没有太困扰我。 我可以通过将 lambda 存储在成员委托中来解决这两个问题(如果它们确实是问题的话),但这会破坏使用 lambda 的便利性和保留类的目的干净整洁。
不过,我认为还有另外两件事可能不那么明显,但可能更有问题。
每个 lambda 函数在其包含范围内形成一个闭包。 这可能意味着在构造函数中之前创建的临时对象的存活时间比它们需要的时间长得多,因为闭包维护着对它们的引用。 现在希望编译器足够聪明,可以从 lambda 不使用的闭包中排除对象,但我不确定。 有人知道吗?
再次幸运的是,这并不总是一个问题,因为我不经常在构造函数中创建临时对象。 我可以想象一个场景,我确实这样做了,但我无法轻松地将其范围限定在 lambda 之外。
- 可维护性可能会受到影响。 重要时刻。 如果我将一些事件处理程序定义为函数,并将一些事件处理程序定义为 lambda,我担心这可能会使追踪错误或理解类变得更加困难。 稍后,如果我的事件处理程序最终扩展,我要么必须将它们移动到类级函数,要么处理我的构造函数现在包含大量实现类功能的代码的事实。
因此,我想借鉴其他人的建议和经验,也许是那些具有函数式编程功能的其他语言经验的人。 对于此类事情是否有任何既定的最佳实践? 您是否会避免在事件处理程序中或在 lambda 显着超出其封闭范围的其他情况下使用 lambda? 如果不是,在什么阈值下您会决定使用实数函数而不是 lambda? 上述任何陷阱是否对任何人造成了严重影响? 还有哪些我没有想到的陷阱吗?
Lambda syntax in C# 3 makes it really convenient to create one-liner anonymous methods. They're a definite improvement over the wordier anonymous delegate syntax that C# 2 gave us. The convenience of lambdas, however, brings with it a temptation to use them in places where we don't necessarily need the functional programming semantics they provide.
For instance, I frequently find that my event handlers are (or at least start out as) simple one-liners that set a state value, or call another function, or set a property on another object, etc. For these, should I clutter my class with yet another simple function, or should I just stuff a lambda into the event in my constructor?
There are some obvious disadvantages to lambdas in this scenario:
- I can't call my event handler directly; it can only be triggered by the event. Of course, in the case of these simple event handlers, there's hardly a time I would need to call them directly.
- I can't unhook my handler from the event. On the other hand, rarely do I ever need to unhook event handlers, so this isn't much of issue, anyway.
These two things don't bother me much, for the reasons stated. And I could solve both of those problems, if they really were problems, by storing the lambda in a member delegate, but that would kind of defeat the purposes of using lambdas for their convenience and of keeping the class clean of clutter.
There are two other things, though, that I think are maybe not so obvious, but possibly more problematic.
Each lambda function forms a closure over its containing scope. This could mean that temporary objects created earlier in the constructor stay alive for much longer than they need to due to the closures maintaining references to them. Now hopefully, the compiler is smart enough to exclude objects from the closure that the lambda doesn't use, but I'm not sure. Does anybody know?
Luckily again, this isn't always an issue, as I don't often create temporary objects in my constructors. I can imagine a scenario where I did, though, and where I couldn't easily scope it outside of the lambda.
- Maintainability might suffer. Big time. If I have some event handlers defined as functions, and some defined as lambdas, I worry it might make it more difficult to track down bugs, or to just understand the class. And later, if and when my event handlers end up expanding, I'll either have to move them to class-level functions, or deal with the fact that my constructor now contains a significant amount of the code that implements the functionality of my class.
So I want to draw on the advice and experience of others, perhaps those with experience in other languages with functional programming features. Are there any established best practices for this kind of thing? Would you avoid using lambdas in event handlers or in other cases where the lambda significantly outlives its enclosing scope? If not, at what threshold would you decide to use a real function instead of a lambda? Have any of the above pitfalls significantly bitten anybody? Are there any pitfalls I haven't thought of?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我通常有一个例程专门用于连接事件处理程序。 其中,我对实际处理程序使用匿名委托或 lambda,使它们尽可能短。 这些处理程序有两个任务:
完成此操作后,我避免了事件处理程序方法使我的类命名空间变得混乱,这些方法不能干净地用于其他目的,并迫使自己思考我所实现的操作方法的需求和目的,通常会产生更干净的代码。
I generally have one routine dedicated to wiring up event handlers. Therein, i use anonymous delegates or lambdas for the actual handlers, keeping them as short as possible. These handlers have two tasks:
This done, i've avoided cluttering up my class namespace with event handler methods that cannot be cleanly used for other purposes, and forced myself to think about the needs and purposes of the action methods that i do implement, generally resulting in cleaner code.
据我所知,C# 编译器要么生成一个匿名方法,要么生成一个匿名内部类,具体取决于它是否需要关闭包含范围。
换句话说,如果您不从 lambda 内部访问包含范围,它就不会生成闭包。
然而,这有点“道听途说”,我希望有更了解 C# 编译器的人参与进来。
尽管如此,旧的 C# 2.0 匿名委托语法做了同样的事情,而且我几乎总是使用匿名委托来处理短事件处理程序。
您已经很好地涵盖了各种优点和缺点,如果您需要取消事件处理程序,请不要使用匿名方法,否则我完全赞成。
From what I have read, the C# compiler either generates an anonymous method, or an anonymous inner class, depending on if it needs to close over the containing scope or not.
In other words, if you don't access the containing scope from within your lambda, it won't generate up the closure.
However, this is a bit of "hearsay", and I'd love to have someone who is more knowledgeable with the C# compiler weigh in on that.
All that said, the old C# 2.0 anonymous delegate syntax did the same thing, and I've almost always uses anonymous delegates for short event handlers.
You have covered the various pros and cons quite well, if you need to unhook your event handler, don't use an anonymous method, otherwise I'm all for it.
基于对编译器的一个小实验,我想说编译器足够聪明,可以创建一个闭包。 我所做的是一个简单的构造函数,它有两个不同的 lambda,用于 List.Find() 中的谓词。
第一个 lamdba 使用硬编码值,第二个在构造函数中使用参数。 第一个 lambda 是作为类上的私有静态方法实现的。 第二个 lambda 被实现为执行结束的类。
所以你关于编译器足够聪明的假设是正确的。
Based on a little experiment with the compiler I would say the compiler is smart enough to create a closure. What I did was a simple constructor which had two different lambdas which were used for a Predicate in List.Find().
The first lamdba used a hard coded value, the second used a parameter in the constructor. The first lambda was implemented as a private static method on the class. The second lambda was implemented as a class which performed the closing.
So your assumption that the compiler is smart enough is correct.
lambda 的大多数相同特征同样适用于您可以使用它们的其他地方。 如果事件处理程序不适合他们,我想不出更好的了。 它是一个单点独立的逻辑单元,位于其单点。
在许多情况下,该活动旨在获得一些适合手头工作的背景信息。
我认为这是重构意义上的“好味道”之一。
Most of the same characteristics of lambdas can apply equally well in other places where you can use them. If event handlers isn't a place for them, I can't think of any better. It's a single-point self-contained unit of logic, located at its single point.
In many cases, the event is designed to get a little package of context that turns out to be just right for the job at hand.
I consider this to be one of the "good smells" in a refactoring sense.
关于 lambda,我最近问的这个问题在接受的答案中包含了一些关于对对象寿命影响的相关事实。
我最近了解到的另一件有趣的事情是,C# 编译器在其捕获和保持活动状态的事物方面将多个闭包理解为与单个闭包相同的范围。 遗憾的是我找不到这个的原始出处。 如果我再次偶然发现它,我会补充这一点。
就我个人而言,我不使用 lambda 作为事件处理程序,因为我觉得当逻辑从请求流向结果时,可读性优势才真正显现出来。 事件处理程序往往添加到构造函数或初始化程序中,但在对象生命周期的此时很少会调用它。 那么,为什么我的构造函数应该读起来像是现在正在做的事情实际上要晚得多呢?
另一方面,我确实使用了一种总体上略有不同的事件机制,我发现它比 C# 语言功能更可取:用 C# 重写的 iOS 风格的通知中心,具有由类型(派生自通知)作为键控的调度表以及行动< 通知> 价值观。 这最终允许单行“事件”类型定义,如下所示:
Concerning lambdas, this question I asked recently has some relevant facts about effects on object lifespan in the accepted answer.
Another interesting thing I recently learned is that the C# compiler comprehends multiple closures in the same scope as a single closure in respect of the things it captures and keeps alive. Sadly I can't find the original source for this. I will add that if I stumble upon it again.
Personally, I don't use lambdas as event handlers because I feel the readability advantage really comes when the logic is flowing from a request to a result. The event handler tends to be added in a constructor or initialiser, but it will rarely be called at this point in the object's lifecycle. So why should my constructor read like it's doing things now that are actually happening much later?
On the other hand, I do use a slightly different kind of event mechanism overall, which I find preferable to the C# language feature: an iOS-style NotificationCenter rewritten in C#, with a dispatch table keyed by Type (derived from Notification) and with Action < Notification > values. This ends up allowing single-line "event" Type definitions, like so: