C#和VB lambdas有**范围链**问题类似于javascript吗?

Pac*_*ier 4 javascript c# vb.net scope scope-chain

我已经读过了由于范围链如何在javascript中工作,如果我们希望引用未在F范围内声明的函数F中的变量V,那么声明一个是有益的(在性能方面是肯定的) F中的局部变量V2引用V,然后访问V引用的对象到V2.

我想知道这个概念是否适用于C#和VB中的闭包(通过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
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果答案不是过早的优化,那么我更愿意是所有邪恶的根源

Jul*_*iet 7

简答:不..NET不需要走向范围链来查找变量.

答案很长:

从这个例子开始:

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
}
Run Code Online (Sandbox Code Playgroud)

在该CaptureArgs方法中,a并且b存在在堆栈中.直观地说,如果我们在一个匿名函数引用变量,返回的功能和出栈帧应该删除a,并b从内存.(这被称为向上的funargs问题).

C#没有遭遇向上的funargs问题,因为在幕后,匿名函数只是编译器生成的类的花哨语法糖.上面的C#代码变成:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器创建并返回一个新实例<>c__DisplayClass1,从其初始化它ab字段ab传入CaptureArgs方法(这有效地从堆栈复制a并传递到b堆中存在的字段),并将其返回给调用者.通话f()真的是一个电话<>c__DisplayClass1.<CaptureArgs>b__0().

由于ab引用的<CaptureArgs>b__0是vanilla字段,它们可以由委托直接引用,它们不需要任何特殊类型的范围链规则.


Tom*_*cek 6

如果我理解正确,JavaScript的问题如下:当您访问(深度)嵌套范围中的变量时,运行时需要遍历所有父范围以定位变量.

C#或Visual Basic中的Lambda不会遇到此问题.

在VB或C#中,编译器确切知道您在lambda函数中引用哪个变量,因此它可以创建对该变量的直接引用.唯一的区别是捕获的变量(从嵌套范围访问的变量)必须从局部变量转换为字段(在某些对象中,也称为闭包).

添加一个例子 - 说你写这样的东西(疯狂):

Func<Func<int>> Foo() {
  int x = 10;
  return () => {
    x++;
    return () => x;
  }
}
Run Code Online (Sandbox Code Playgroud)

这有点傻,但它演示了嵌套的范围.变量在一个作用域中声明,在嵌套作用域中设置并在更深的范围内读取.编译器将生成如下内容:

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);
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的 - 没有走过一系列范围.每次访问变量时x,运行时都会直接访问内存中的某个位置(在堆上分配,而不是堆栈).