4

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

我想知道这个概念是否适用于 C# 和 VB 中的闭包(通过 lambda 访问函数中的局部变量)

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

顺便说一句,如果答案不是过早的优化是万恶之源,我更愿意

4

3 回答 3

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
}

CaptureArgs方法中,a并存b在于栈上。直观地说,如果我们在匿名函数中引用变量,则返回该函数并弹出堆栈帧应该从内存中删除a和。b(这称为向上的函数参数问题)。

C# 不会遇到向上的函数参数问题,因为在幕后,匿名函数只是编译器生成的类的花哨语法糖。上面的 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);
    }
}

编译器创建并返回 的一个新实例<>c__DisplayClass1,从 初始化它的ab字段a并将其b传递给方法(这实际上将和从堆栈CaptureArgs复制到堆上存在的字段),并将其返回给调用者。呼唤真的是呼唤。abf()<>c__DisplayClass1.<CaptureArgs>b__0()

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

于 2011-05-07T21:56:23.483 回答
6

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

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

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

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

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

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

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);
}

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

于 2011-05-07T21:36:53.060 回答
3

简短的回答:没有。

C# 闭包以静态[[scope chain]]方式实现(对变量的闭包是明确已知和绑定的)并且不会像在 Javascript中那样遍历 a 。

运行一些测试让您放心,然后就不用担心了 ;-)

快乐编码。

于 2011-05-07T21:36:05.290 回答