我认为 C# 具有词法作用域,但为什么这个示例显示动态作用域行为?

问题描述 投票:0回答:2
    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,我假设它是2 /lec/lec12.pdf

c# scope closures
2个回答
26
投票

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
xDefineIt
变量
。定义时的值 (
x = 1
) 是无关紧要的。 变量稍后设置为
x = 3

但它显然也不是动态范围,因为

x = 2
内部的
InvokeIt
没有被使用。


24
投票

这个问题是我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——在打印机上运行的语言——是动态范围的。

© www.soinside.com 2019 - 2024. All rights reserved.