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.
精彩评论