Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

does C# and VB lambdas have **scope chain** issues similar to javascript?

I've read that due to how the scope chain works in javascript, if we wish to refer to a variable V within a function F that is not declared within the F's scope, it is beneficial (yes in terms of performance) to declare a local variable V2 in F that references V, then accessing the object referenced by V through V2.

i'm wondering if this concept applies to the closures in C# and VB (accessing local variables in functions through lambdas)

Public Shared Function Example()
    Dim a = 1
    Dim b = New Object
    Return Sub()
               'when we use the variables a and b from here does it have to "go up the scope chain"
           End Sub
End Function

btw i would prefer if the answer isn't premature optimization is the root of all evil

like image 554
Pacerier Avatar asked Dec 05 '22 22:12

Pacerier


1 Answers

Short answer: no. .NET doesn't need to walk up the scope chain to find the variables.

Long answer:

Start with this example:

static Func<string> CaptureArgs(int a, int b)
{
    return () => String.Format("a = {0}, b = {1}", a, b);
}

static void Main(string[] args)
{
    Func<string> f = CaptureArgs(5, 10);
    Console.WriteLine("f(): {0}", f());
    // prints f(): a = 5, b = 10
}

In the CaptureArgs method, a and b exist on the stack. Intuitively, if we reference the variables in an anonymous function, return the function and popping the stack frame should remove a and b from memory. (This is called the upward funargs problem).

C# doesn't suffer from the upwards funargs problem because, behind the scenes, an anonymous function is just fancy syntax sugar over a compiler-generated class. The C# code above turns into:

private sealed class <>c__DisplayClass1
{
    // Fields
    public int a;
    public int b;

    // Methods
    public string <CaptureArgs>b__0()
    {
        return string.Format("a = {0}, b = {1}", this.a, this.b);
    }
}

The compiler creates and returns a new instance of <>c__DisplayClass1, initializes its a and b fields from the a and b passed into the CaptureArgs method (this effectively copies a and b from the stack to fields existing on the heap), and returns it to the caller. Calling f() is really a call to <>c__DisplayClass1.<CaptureArgs>b__0().

Since the a and b referenced in <CaptureArgs>b__0 are vanilla fields, they can be referenced directly by the delegate, they don't require any special sort of scope chaining rules.

like image 70
Juliet Avatar answered Dec 07 '22 23:12

Juliet