Which is the code that creates captured variables / closures?
Asked Answered
G

2

6

I understand that variable capturing is done by the compiler and not by the classes in the .NET framework itself. However, when the DLR was introduced, some of this work must surely have needed to have been done within the framework so as to defer it to runtime.

For e.g., in the piece of code given below:

dynamic d = ...

Func<int, bool> func = n => n > d;

The type resolution of the variable d and its verification that it is an integer must have to be done at run-time. And since d is a variable in the containing method of the lambda, it will be captured into a closure. This part will surely be done at run time.

Hence, I infer there must be some part of the DLR assemblies (System.Core.dll mostly) that does this part.

I've been searching and I could find some classes that look suspiciously reprehensible for this sort of a task. Specifically, ExpressionQuoter (despite the looks of it, this class does not quote lambda expressions like the Expression.Quote method does), HoistedLocals, and the VariableBinder.

I thought I would invite someone who knows better to answer this.

Which class or part of the .NET framework turns locals that are in containing methods of lambdas (or anonymous methods) into those separate classes that have static variables representing them?

Gutshall answered 25/7, 2015 at 11:23 Comment(0)
B
0

And since d is a variable in the containing method of the lambda, it will be captured into a closure.

d does not need to be captured because the lambda is not using it. If the C# compiler chooses to capture it (which is not forbidden under the as-if rule) it will not be accessed in any way.

I believe func(d) is executed like any method call with a dynamic argument would be.

Let's look at:

dynamic d = ...; //maybe "1"

Func<bool> func = (() => d == "1234");

I think this is more in the spirit of what you want to know (Update: Indeed you just edited the question to have this pattern). The lambda depends on d now which was not the case before.

Here, d is captured in the generated closure class as a field of type object. dynamic is always compiled as object (potentially with a custom attribute on it). The lambda code body then proceeds to use standard dynamic operations.

Because all variable references in C# are statically bound to a particular variable there is never a need to capture a dynamic number of fields or something like that. The fields to capture are statically known.

Baptize answered 25/7, 2015 at 12:11 Comment(3)
Sorry for that typo earlier. I was fixing my code snippet after you first commented on the question.Gutshall
No worries. Does this answer the question?Baptize
Yes, sir. It does. I forgot that dynamic is a placeholder for object as far as the compiler is concerned. Thank you.Gutshall
D
2

Which class or part of the .NET framework turns locals that are in containing methods of lambdas (or anonymous methods) into those separate classes that have static variables representing them?

No, it's the compiler that does the job.

How would the values of the variables be passed to the separate method? The only way to do this is to define a new helper class that also defines a field for each value that you want passed to the callback code. In addition, the callback code would have to be defined as an instance method in this helper class. Then, the UsingLocalVariablesInTheCallbackCodemethod would have to construct an instance of the helper class,initialize the fields fromthe values in its local variables, and then construct the delegate objectbound to the helper object/instance method.

This is very tedious and error-prone work, and, of course, the C# compiler does all this for you automatically

From the book CLR Via C#

With your code, there is a class generated that looks like:

class SomeClass
{
    public dynamic d;
    public bool yourCallBack(int n)
    {
        return n > d;
    }
}

and your code is compiled into something like:

dynamic d = ...
SomeClass class1= new SomeClass(); 
class1.d = d;
Func<int, bool> func = class1.yourCallBack;

There is also a note regarding the lifetime of captured variables:

When a lambda expression causes the compiler to generate a class with parameter/local variables turned into fields, the lifetime of the objects that the variables refer to are lengthened. Usually, a parameter/local variable goes out of scope at the last usage of the variable within a method. However, turning the variable into a field causes the field to keep the object that it refers to alive for the whole lifetime of the object containing the field. This is not a big deal in most applications, but it is something that you should be aware of.

Diazomethane answered 25/7, 2015 at 11:56 Comment(2)
I believe Jeffrey is talking about the way the compiler generates the same thing. Until C# 3.0, the compiler was the only place that had this transformation. From C# 4, I believe they had reason to move a lot of what the compiler does into the DLR so as to support runtime binding of operations on the dynamic type.Gutshall
Thank you for all this. It really misses the point of my question, though. You're explaining how closures work, which isn't my question.Gutshall
B
0

And since d is a variable in the containing method of the lambda, it will be captured into a closure.

d does not need to be captured because the lambda is not using it. If the C# compiler chooses to capture it (which is not forbidden under the as-if rule) it will not be accessed in any way.

I believe func(d) is executed like any method call with a dynamic argument would be.

Let's look at:

dynamic d = ...; //maybe "1"

Func<bool> func = (() => d == "1234");

I think this is more in the spirit of what you want to know (Update: Indeed you just edited the question to have this pattern). The lambda depends on d now which was not the case before.

Here, d is captured in the generated closure class as a field of type object. dynamic is always compiled as object (potentially with a custom attribute on it). The lambda code body then proceeds to use standard dynamic operations.

Because all variable references in C# are statically bound to a particular variable there is never a need to capture a dynamic number of fields or something like that. The fields to capture are statically known.

Baptize answered 25/7, 2015 at 12:11 Comment(3)
Sorry for that typo earlier. I was fixing my code snippet after you first commented on the question.Gutshall
No worries. Does this answer the question?Baptize
Yes, sir. It does. I forgot that dynamic is a placeholder for object as far as the compiler is concerned. Thank you.Gutshall

© 2022 - 2024 — McMap. All rights reserved.