var x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
PDF 没有完全解释有关词法范围的微妙之处。它的示例实际上有两个名为
x
的不同变量,它不会重新分配第一个 x
的值(实际上函数式语言可能不允许突变)。
C# 是词法作用域的——它在 lambda 定义点查找
x
,而不是在调用委托时查找。但是: 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()));
lambda 绑定到存在于
inside
x
的DefineIt
变量。定义时的值 (
x = 1
) 是无关紧要的。 变量稍后设置为 x = 3
。
但它显然也不是动态范围,因为
x = 2
内部的InvokeIt
没有被使用。
这个问题是我2013年5月20日博客的主题。感谢您提出这个好问题!
您误解了“词法范围”的含义。让我们引用您链接到的文档:
函数体是在定义函数时存在的旧动态环境中计算的,而不是在调用函数时的当前环境中计算的。
这是您的代码:
int x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
现在,什么是“定义函数时存在的动态环境”?将“环境”视为一个类。该类包含每个变量的可变字段。所以这与:
相同Environment e = new Environment();
e.x = 1;
Func<int,int> f = y => e.x + y;
e.x = 2;
Console.WriteLine(f(1));
当评估
f
时,会在创建 f 时存在的环境 e 中查找x
。该环境的内容已更改,但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));
}
}
如果 C# 是动态作用域的,那么这将打印“103”,因为评估
g
会评估
f
,而在动态作用域语言中,评估 f
将在当前环境中查找
x
的值。在 current 环境中,
x
为 3。在创建 f
时存在的环境中,x
为 2。同样,该环境中 x
的值已更改;正如您的文档所指出的,该环境是一个“动态环境”。但是相关的环境不会改变。 当今大多数语言都不是动态作用域的,但也有一些。例如,PostScript——在打印机上运行的语言——是动态范围的。