We were unable to load Disqus. If you are a moderator please see our troubleshooting guide.

Andreas Horwath • 7 years ago

Scary indeed! And totally surprising! I have played around with your code a bit, and it turns out that you get the same behavior if you replace LocalFunctionThatUsesTheResourceDemandingObject with a lambda expression. Local functions really seem to behave no differently than lambdas as far as variable capturing is concerned. But why does the compiler work this way? My guess is that it may have something to do with the fact that in the most general case a single variable may be shared by multiple lambdas (or local functions). For example, suppose you have a method declaring three local variables a, b, and c as well as two lamdas – one that captures a and b, and another one that captures b and c. The only way (as far as I can see) for the compiler to support this scenario is to generate a single capturing object holding all three variables. I realize that in your code no such sharing of captured variables occurs, but maybe the most general case is simply assumed to be the default and is invariably applied for reasons of consistency. All of that is speculation, of course. What matters to me, though, is that this can be a subtle source of hard-to-detect memory leaks! Thanks for pointing it out.

Igor Rončević • 7 years ago

BDW, speaking of the static analysis and memory leaks, this might be interesting for you: Roslyn Clr Heap Allocation Analyzer

It is a Roslyn based C# heap allocation diagnostic analyzer that can detect explicit and many implicit allocations like boxing, display classes a.k.a closures, implicit delegate creations, etc.

Pretty cool thing :-) I've listed it on the Awesome Roslyn list.

Igor Rončević • 7 years ago

Hi Andreas,

Thanks for your insights! Yes you are right, when it comes to capturing objects local functions behave exactly the same as lambdas, but only in the cases when they are used in a method together with lambdas ;-) Why is it so, we can only speculate.

To your example with the local variables a, b, and c - Yes, in a general case lambdas can overlap in their usage of variables and in that case, as you mentioned, generating a single closure makes sense. But even if they do not overlap I would personally do the same. For example, if we had three lambdas each of them using only a or b or c I would still pack all the three variables together. Why? If we separate them into three closure classes that would mean always having three heap allocations for the sake of avoiding potentially possible memory leaks. This would mean hurting performance by default just to avoid potential problems ;-) Let's then rather pack the variables together and leave the potential issues to static code analyzers.

But in my example we have a different story. It is like having two variables a and b used in two non-overlapping cases (lambda and a local function). This means the one used in the local function can be allocated on the stack as it would be if lambda were not there. In this case I would expect the variables to be separated, one in the closure and one on the stack.

I don't see any valid reason not to have it that way. One reason could be keeping the compiler implementation simple and consistent. Yet, knowing how many difficult places and optimizations are already there in the compiler I do not believe that handling local functions and lambdas separately would increase the complexity of the compiler.

But as you said, this is all just pure guessing an speculation :-)