Eri*_*ert 23

这个问题是我2013年5月20日博客的主题.谢谢你这个好问题!


你误解了"词法范围"意味着什么.让我们引用您链接到的文档:

函数的主体在定义函数时存在的旧动态环境中进行评估,而不是在调用函数时的当前环境.

这是你的代码:

int  x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
Run Code Online (Sandbox Code Playgroud)

现在,什么是"定义函数时存在的动态环境"?把"环境"想象成一个阶级.该类包含每个变量的可变字段.所以这和:

Environment e = new Environment();
e.x = 1;
Func<int,int> f = y => e.x + y;
e.x = 2;
Console.WriteLine(f(1));
Run Code Online (Sandbox Code Playgroud)

在f评估时,x查找创建f时存在的环境e.该环境的内容已更改,但绑定的环境f是相同的环境.(请注意,这实际上是C#编译器生成的代码!当您在lambda中使用局部变量时,编译器会生成一个特殊的"环境"类,并将本地的每个用法转换为字段的用法.)

让我举个例子说明如果C#是动态范围的,那么世界会是什么样子.考虑以下:

class P
{
    static void M()
    {
        int x = 1;
        Func<int, int> f = y => x + y;
        x = 2;
        N(f);
    }
    static void N(Func<int, int> g)
    {
        int x = 3;
        Console.WriteLine(g(100));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果C#是动态范围的,那么这将打印"103",因为评估g评估f,并且在动态范围的语言中,评估f将查找x 当前环境中的值.在当前环境中,x是3.在f创建时存在的环境中,x是2.再次,该x环境中的值已经改变; 正如您的文档所指出的,环境是一个动态的环境.但哪个环境是相关的并没有改变.

如今大多数语言都不是动态范围的,但也有一些.PostScript,例如 - 在打印机上运行的语言 - 是动态范围的.

  • @colinfang:这样想吧.如果你说`int x = 1; F(X); x = 2; f(x);`你会期望结果与`f(1)相同; f(2);`,对吧?如果你说`int x = 1; 行动a =()=> {f(x);}; 一个(); x = 2; a();`那么为什么你会期望这是不同的?难道你不能在不改变代码含义的情况下*将重复的代码提取到lambda*中吗?新代码具有"f(1)"的含义会很奇怪; f(1);`,对吧? (4认同)

Ben*_*igt 22

有关词汇范围的微妙之处,PDF没有完全解释.它的例子实际上有两个不同的变量命名x,它不重新分配第一个的值x(实际上功能语言可能不允许变异).

C#是词法范围的 - 它查找xlambda的定义点,而不是在调用委托时.但是:x解析为变量而不是值,并且它在调用时读取变量的值.

这是一个更完整的例子:

int InvokeIt( Func<int, int> f )
{
   int x = 2;
   return f(1);
}

Func<int, int> DefineIt()
{
   int x = 1;
   Func<int, int> d = (y => x + y);
   x = 3;  // <-- the PDF never does this
   return d;
}

Console.WriteLine(InvokeIt(DefineIt()));
Run Code Online (Sandbox Code Playgroud)

lambda绑定到内部存在的x 变量.定义点的值()无关紧要.该变量稍后设置为. DefineItx = 1x = 3

但它显然也不是动态范围,因为x = 2内部InvokeIt没有使用.