var x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
输出为 3。根据https://web.archive.org/web/20170426121932/http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec,我假设它是 2 /lec12.pdf
var x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));
输出为 3。根据https://web.archive.org/web/20170426121932/http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec,我假设它是 2 /lec12.pdf
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 绑定到内部存在的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
查找。那个环境的内容已经改变了,但是绑定的环境是同一个环境。(请注意,这实际上是 C# 编译器生成的代码!当您在 lambda 中使用局部变量时,编译器会生成一个特殊的“环境”类,并将本地的每次使用转换为字段的使用。)f
让我举一个例子,看看如果 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
查找 的值。在当前环境中,是 3。在创建时存在的环境中,是 2。同样,在那个环境中的值已经改变;正如您的文件指出的那样,环境是一个动态环境。但是哪个环境是相关的并没有改变。x
x
f
x
x
如今,大多数语言都不是动态范围的,但也有一些。例如,PostScript——在打印机上运行的语言——是动态范围的。