【问题标题】:does C# and VB lambdas have **scope chain** issues similar to javascript?C# 和 VB lambda 是否有类似于 javascript 的**范围链**问题?
【发布时间】:2011-05-07 20:24:54
【问题描述】:

我已经读过,由于 作用域链 在 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

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

,我更愿意

【问题讨论】:

  • 您是在谈论性能方面的问题,还是...?
  • 请提供代码示例。另外,“它是有益的”是什么意思?你说的好处是什么?
  • @Matti Virkkunen 我已经编辑了问题
  • @Cheeso 我已经编辑了问题
  • 现在你已经更新了你的问题:我不知道,但你应该很容易测试。

标签: c# javascript vb.net scope scope-chain


【解决方案1】:

简短的回答:不。 .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 方法中,ab 存在于堆栈中。直观地说,如果我们在匿名函数中引用变量,返回函数并弹出堆栈帧应该会从内存中删除 ab。 (这称为upward funargs problem)。

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

编译器创建并返回&lt;&gt;c__DisplayClass1 的新实例,从传递给CaptureArgs 方法的ab 初始化其ab 字段(这有效地复制了ab 从堆栈到堆上存在的字段),并将其返回给调用者。调用f() 确实是调用&lt;&gt;c__DisplayClass1.&lt;CaptureArgs&gt;b__0()

由于&lt;CaptureArgs&gt;b__0 中引用的ab 是普通字段,它们可以被委托直接引用,它们不需要任何特殊的范围链接规则。

【讨论】:

  • 这是否意味着每次创建 lambda 时都会生成一个新类?是否不鼓励在 C#/VB 程序中大量使用 lambda?
【解决方案2】:

如果我理解正确,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 时,运行时都会直接访问内存中的某个位置(分配在堆上,而不是堆栈上)。

【讨论】:

  • 这是否意味着每次创建 lambda 时都会生成一个新类?是否不鼓励在 C#/VB 程序中大量使用 lambda?
  • 我相信编译器会将一些 lambda 组合成一个类。这绝对不是避免使用 lambdas 的理由——编译后的类不会增加太多的程序大小,如果没有 lambdas,您将不得不编写更多代码。
【解决方案3】:

简短回答:不。

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

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

编码愉快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-12
    • 1970-01-01
    • 2022-08-02
    • 1970-01-01
    • 2017-09-09
    相关资源
    最近更新 更多