views:

688

answers:

4

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?

+2  A: 

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?

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.

FlySwat
C# compiler gives access to the variables in scope *only if you use it in lambda*, otherwise, it's not a problem (this applies to anonymous methods and lambdas).
Mehrdad Afshari
+3  A: 

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:

  1. Unpack event parameters.
  2. Call a named method with appropriate parameters.

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.

Shog9
+1. This is similar to what I do in my WndProc in native code.
P Daddy
Heh, yeah... that's where i'm coming from.
Shog9
A: 

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.

le dorfier
+2  A: 

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.

JoshBerke
That confirms what I suspected below. Thanks for checking out the IL.
FlySwat
No problem, I figured it would work this way as well but wanted to confirm for my own curiosity
JoshBerke