开发者

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

开发者 https://www.devze.com 2023-03-03 11:36 出处:网络
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 pe

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开发者_C百科 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


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.


If I understand it correctly, the problem with JavaScript is following: When you access a variable in a (deeply) nested scope, the runtime needs to walk through all parent scopes to locate the variable.

Lambdas in C# or Visual Basic do not suffer from this issue.

In VB or C#, the compiler knows exactly which variable are you referring to in a lambda function, so it can create a direct reference to the variable. The only difference is that captured variables (those accessed from nested scope) have to be turned from a local variable into a field (in some object, also called a closure).

To add an example - say you write something (crazy) like this:

Func<Func<int>> Foo() {
  int x = 10;
  return () => {
    x++;
    return () => x;
  }
}

This is a bit silly, but it demonstrates nested scoping. The variable is declared in one scope, set in a nested scope and read in an even deeper scope. The compiler will produce something like this:

class Closure { 
  public Closure(int x) { this.x = x; }
  public int x;  
  public Func<int> Nested1() { 
    x++;
    return Func<int>(Nested2);
  }
  public int Nested2() { return x; }
}

Func<Func<int>> Foo() {
  var clo = new Closure(10);
  return Func<Func<int>>(clo.Nested1);
}

As you can see - there is not walking through a chain of scopes. Each time you access the variable x, the runtime directly accesses some location in memory (allocated on the heap, instead of a stack).


Short answer: No.

C# closures are implemented in a static fashion (closed over variables are explicitly known and bound) and does not traverse through a [[scope chain]], as in Javascript.

Run some tests to put your mind at ease, then just don't worry about it ;-)

Happy coding.

0

精彩评论

暂无评论...
验证码 换一张
取 消